// ----------------------------------
// RSDK Project: Sonic 1/Sonic 2
// Script Description: Player Object Object
// Script Author: Christian Whitehead/Simon Thomley
// Unpacked by Rubberduckycooly's script unpacker
// ----------------------------------

// Aliases
private alias 1 		: TYPE_PLAYEROBJECT
public alias  256 		: GROUP_PLAYERS
public alias  65536 	: HITBOX_AUTO
public alias  arrayPos6 : currentPlayer
public alias  arrayPos7 : playerCount
public alias saveRAM[257] : saved.ssmusic

// Draw Order
private alias -1 : DRAWORDER_PLAYER

// Players
private alias 0 : PLAYER_SONIC_ALONE
private alias 1 : PLAYER_TAILS_ALONE
private alias 2 : PLAYER_KNUX_ALONE
private alias 3 : PLAYER_SONIC_AND_TAILS

// Shields
private alias 0 : SHIELD_NONE
private alias 1 : SHIELD_NORMAL
private alias 2 : SHIELD_BUBBLE
private alias 3 : SHIELD_FIRE
private alias 4 : SHIELD_LIGHTNING
private alias 5 : SHIELD_GOGGLES // LZ goggles, debug mode only

// Variables
private alias object.type			 :	player.type		 		// Normally just TypeName[Player Object], TypeName[Player 2 Object], or TypeName[Tails Object] but may be TypeName[Debug Mode] or other things in certain situations
private alias object.groupID		 :	player.groupID	 		// The player's active group, normally GROUP_PLAYERS
private alias object.entityPos		 :	player.entityPos 		// Where the player is on the object list. P1 should be 0, P2 should be 1
private alias object.state			 :	player.state
private alias object.visible		 :	player.visible
private alias object.propertyValue	 :	player.character
private alias object.xpos			 :	player.xpos				// Total world-space position (0x10000 == 1.0)
private alias object.ypos			 :	player.ypos
private alias object.ixpos			 :	player.ixpos			// Screen space position (1 == 1)
private alias object.iypos			 :	player.iypos
private alias object.lookPosX		 :	player.lookPosX			// Camera offset based on this player's position
private alias object.lookPosY		 :	player.lookPosY
private alias object.xvel			 :	player.xvel				// Based on world-space (see above)
private alias object.yvel			 :	player.yvel
private alias object.speed			 :	player.speed			// Based on world-space (see above)
private alias object.rotation		 :	player.rotation			// Note: Because this is stored as a byte, it goes from 0-255, not 0-360!
private alias object.angle			 :	player.angle			// See above
private alias object.direction		 :	player.direction
private alias object.gravity		 :	player.gravity			// 1 if falling, 0 otherwise. Not to be confused with player.gravityValue, see there for more info
private alias object.frame			 :	player.frame
private alias object.animation		 :	player.animation
private alias object.prevAnimation	 :	player.prevAnimation
private alias object.animationSpeed	 :	player.animationSpeed
private alias object.animationTimer	 :	player.animationTimer
private alias object.drawOrder		 :	player.drawOrder
private alias object.pushing		 :	player.pushing
private alias object.controlLock	 :	player.controlLock
private alias object.controlMode	 :	player.controlMode
private alias object.interaction	 :	player.interaction		// Will the object interact with tiles?
private alias object.scrollTracking	 :	player.scrollTracking	// Determines if the camera will track the player's position or just follow it
private alias object.collisionMode	 :	player.collisionMode
private alias object.collisionLeft	 :	player.collisionLeft
private alias object.collisionTop	 :	player.collisionTop
private alias object.collisionRight	 :	player.collisionRight
private alias object.collisionBottom :	player.collisionBottom
private alias object.collisionPlane	 :	player.collisionPlane
private alias object.floorSensorC	 :	player.floorSensorC
private alias object.floorSensorL	 :	player.floorSensorL
private alias object.floorSensorR	 :	player.floorSensorR
private alias object.tileCollisions	 :	player.tileCollisions
private alias object.priority		 :	player.priority

// *Object-wise* input, not to be confused with inputPress.X and inputDown.X
private alias object.jumpPress	:	player.jumpPress
private alias object.jumpHold	:	player.jumpHold
private alias object.up			:	player.up
private alias object.down		:	player.down
private alias object.left		:	player.left
private alias object.right		:	player.right

// Object values, not all are used or documented
private alias object.value0   :	player.rings
private alias object.value1  :	player.timer
private alias object.value2  :	player.abilityTimer			// Note: Also used by death/drowning state
private alias object.value3  :	player.drownCounter			// Countdown before player moves to next draown "level"
private alias object.value4  :	player.drownLevel
private alias object.value5  :	player.CustomRollAnimSpeed
private alias object.value6  :	player.speedShoes
private alias object.value7  :	player.invincible
private alias object.value8  :	player.invulnerable
private alias object.value9  :	player.baseSpeed
private alias object.value10 :	player.animationReserve		// Used by springs to store what animation will play after the bounce animation
private alias object.value11 :	camera.lockTimer			// A timer of how long the camera will stay locked for
private alias object.value14 :	player.skidding
private alias object.value16 :	player.isPlayer2			// 0/false is player 1, 1/true if player 2
private alias object.value17 :	debugMode.currentSelection
private alias object.value18 :	player.sortedDrawOrder
private alias object.value19 :	player.enemyChainCounter	// How many enemies the player has bounced on in a row
private alias object.value20 :	player.speedCap 			// Not to be confused with options.speedCap
private alias object.value21 :	player.acceleration
private alias object.value22 :	player.deceleration
private alias object.value23 :	player.airAcceleration
private alias object.value25 :	player.gravityValue 		// Also used in underwater checks , 0x1000 if underwater, otherwise 0x3800. Not to be confused with player.gravity
private alias object.value26 :	player.flightVelocity		// Used by Tails only
private alias object.value27 :	player.jumpVelocity
private alias object.value28 :	player.jumpStrength
private alias object.value29 :	player.rollingDecelerationA	// Active rolling deceleration - With the player holding the opposite direction
private alias object.value30 :	player.jumpOffset			// Added to the player's position when jumping/rolling. Normally -5 for S&K, and -1 for Tails
private alias object.value31 :	player.rollingDecelerationP	// Passive rolling deceleration - Without the player holding the opposite direction
private alias object.value32 :	player.jumpAbility			// Used to store whatever function this player has for its jump ability
private alias object.value33 :	player.spindashFunction		// Used to store whatever function this player has for its spindash ability
private alias object.value34 :	player.collisionEnabled		// Could be named better
private alias object.value35 :	player.doubleJumpState		// Could be named better
private alias object.value36 :	player.tailsGrabTimeout		// Tails assist lockout timer
private alias object.value37 :	player.shield				// Current shield the player has, see above constants for what is what
private alias object.value38  :	player.hitboxLeft
private alias object.value39  :	player.hitboxRight
private alias object.value40  :	player.hitboxTop
private alias object.value41  :	player.hitboxBottom
private alias object.value42 :	player.prevGravity

// Death Event related values
private alias object.drawOrder : deathEvent.drawOrder
private alias object.state	   : deathEvent.state
private alias object.value1	   : deathEvent.leftTextPos
private alias object.value2	   : deathEvent.rightTextPos
private alias object.value3	   : deathEvent.timer

// Function declarations
reserve function PlayerObject_ProcessPlayer
reserve function PlayerObject_Blank
reserve function PlayerObject_HandleMovement
reserve function PlayerObject_HandleAirAcceleration
reserve function PlayerObject_HandleAirMovement
reserve function PlayerObject_ResetOnFloor
reserve function PlayerObject_StartJump
reserve function PlayerObject_StartSpindash
reserve function PlayerObject_StartTailsFlight
reserve function PlayerObject_StartKnuxGlide
reserve function PlayerObject_HandleGround
reserve function PlayerObject_HandleAir
reserve function PlayerObject_AirCtrlLock
reserve function PlayerObject_HandleRoll
reserve function PlayerObject_RollingJump
reserve function PlayerObject_LookingUp
reserve function PlayerObject_Crouching
reserve function PlayerObject_HandleSpindash
reserve function PlayerObject_HandleTailsFlight
reserve function PlayerObject_KnuxGlideLeft
reserve function PlayerObject_KnuxGlideRight
reserve function PlayerObject_KnuxGlideDrop
reserve function PlayerObject_KnuxGlideSlide
reserve function PlayerObject_KnuxWallClimb
reserve function PlayerObject_KnuxLedgePullUp
reserve function PlayerObject_Hurt
reserve function PlayerObject_Knockback
reserve function PlayerObject_Death
reserve function PlayerObject_Drown
reserve function PlayerObject_CheckTile
reserve function PlayerObject_Unknown1 // Unused - "PlayerObject_CorkscrewRun"
reserve function PlayerObject_RollUnknown // Unused - "PlayerObject_CorkscrewRoll"
reserve function PlayerObject_Unknown2 // "PlayerObject_HandleRollDecel"
reserve function PlayerObject_Clinging
reserve function PlayerObject_WaterSlide
reserve function PlayerObject_FlyCarry
reserve function PlayerObject_HandleRunSpd2
reserve function PlayerObject_InitReplay
reserve function PlayerObject_RestorePowerup
reserve function PlayerObject_HandleSuperPalSonic
reserve function PlayerObject_HandleSuperPalTails
reserve function PlayerObject_HandleSuperPalKnux
reserve function PlayerObject_SwapPhysics
reserve function PlayerObject_ProcessSuperForms
reserve function PlayerObject_CheckHit
reserve function PlayerObject_BadnikBreak
reserve function PlayerObject_Hit
reserve function PlayerObject_FireHit
reserve function PlayerObject_LightningHit
reserve function PlayerObject_ProjectileHit
reserve function PlayerObject_SpikeHit
reserve function PlayerObject_Kill
reserve function PlayerObject_HandleRollAnimSpd
reserve function PlayerObject_HandleWalkAnimSpd
reserve function PlayerObject_HandleRunAnimSpd
reserve function PlayerObject_HandleRolling
reserve function PlayerObject_Transforming
reserve function PlayerObject_SuperTransform
reserve function PlayerObject_BubbleBounce
reserve function PlayerObject_HandleShieldAbilities
reserve function PlayerObject_StartFlyCarry
reserve function PlayerObject_HyperDoubleJump
reserve function PlayerObject_StartPeelout
reserve function PlayerObject_HandlePeelout

// Static Values
public value PlayerObject_LeaderX = 0
public value PlayerObject_LeaderY = 0
public value PlayerObject_SidekickX = 0
public value PlayerObject_SidekickY = 0
public value PlayerObject_SuperState = 0
public value PlayerObject_SuperRingDecFrames = 0
public value PlayerObject_SuperCounter1 = 0
public value PlayerObject_SuperCounter2 = 0
public value PlayerObject_ReplayTable = 0
public value PlayerObject_ReplayTablePos = 0
public value PlayerObject_ReplaySize = 0
public value PlayerObject_ReplayFrameCnt = 0
public value PlayerObject_ReplayLength = 0

// All these below are unused
public value PlayerObject_value13 = 0
public value PlayerObject_value14 = 0
public value PlayerObject_value15 = 0
public value PlayerObject_value16 = 0
public value PlayerObject_value17 = 0
public value PlayerObject_value18 = 0
public value PlayerObject_value19 = 0

// Tables
public table PlayerObject_SonicSuperPal
	0x242490, 0x2448D8, 0x6C6CFC, 0x8080E0
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xfcfcfc
	0xeecc44, 0xfcfc90, 0xfcfcfc, 0xC0C0E0
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xfcfcfc
	0xeeaaaa, 0xeecccc, 0xfcfcfc, 0xE0E0E0 // 1
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xfcfcfc // 2
	0xccaacc, 0xeeccee, 0xfcfcfc, 0xE0E0E0 // 3
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 4
	0x90b4fc, 0xb4d8fc, 0xcceeee, 0xE0E0E0 // 5
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 6
	0x00ee22, 0xaaeeaa, 0xcceecc, 0xE0E0E0 // 7
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 8
	0xcccc00, 0xccee44, 0xcceeaa, 0xE0E0E0 // 9
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 10
	0xaaaa00, 0xfcfc00, 0xfcfc90, 0xE0E0E0 // 11
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 12
end table

// Alt underwater palette
// Normally only used by LZ in S1, the actual UW colors are set in LZSetup
public table PlayerObject_SonicSuperAltPal
	0x00486c, 0x006c90, 0x2490b4, 0x8080E0
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xfcfcfc
	0xeecc44, 0xfcfc90, 0xfcfcfc, 0xC0C0E0
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xfcfcfc
	0xeeaaaa, 0xeecccc, 0xfcfcfc, 0xE0E0E0 // 1
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xfcfcfc // 2
	0xccaacc, 0xeeccee, 0xfcfcfc, 0xE0E0E0 // 3
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 4
	0x90b4fc, 0xb4d8fc, 0xcceeee, 0xE0E0E0 // 5
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 6
	0x00ee22, 0xaaeeaa, 0xcceecc, 0xE0E0E0 // 7
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 8
	0xcccc00, 0xccee44, 0xcceeaa, 0xE0E0E0 // 9
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 10
	0xaaaa00, 0xfcfc00, 0xfcfc90, 0xE0E0E0 // 11
	0xfcfcfc, 0xfcfcfc, 0xfcfcfc, 0xE0E0E0 // 12
end table

public table PlayerObject_TailsSuperPal
	0x800000, 0xE08000, 0xE0A000, 0xA06040
	0x800000, 0xE0A020, 0xE0C040, 0xA06040
	0xA06040, 0xE0C040, 0xE0C080, 0xC06040
	0xC06040, 0xE0C080, 0xE0E0A0, 0xE08040
	0xA06040, 0xE0C040, 0xE0C080, 0xC06040
	0x800000, 0xE0A020, 0xE0C040, 0xA06040
end table

// Alt underwater palette
// Normally only used by LZ in S1, the actual UW colors are set in LZSetup
public table PlayerObject_TailsSuperAltPal
	0x800000, 0xE08000, 0xE0A000, 0xA06040
	0x800000, 0xE0A020, 0xE0C040, 0xA06040
	0xA06040, 0xE0C040, 0xE0C080, 0xC06040
	0xC06040, 0xE0C080, 0xE0E0A0, 0xE08040
	0xA06040, 0xE0C040, 0xE0C080, 0xC06040
	0x800000, 0xE0A020, 0xE0C040, 0xA06040
end table

public table PlayerObject_KnuxSuperPal
	0x600020, 0xC00020, 0xE04060
	0x802040, 0xE04060, 0xE060A0
	0xA04060, 0xE06080, 0xE080C0
	0xC06080, 0xE080A0, 0xE0A0E0
	0xE080A0, 0xE0A0C0, 0xE0C0E0
	0xE0A0C0, 0xE0C0E0, 0xE0E0E0
	0xE080A0, 0xE0A0C0, 0xE0C0E0
	0xC06080, 0xE080A0, 0xE0A0E0
	0xA04060, 0xE06080, 0xE080C0
	0x802040, 0xE04060, 0xE060A0
end table

// Alt underwater palette
// Normally only used by LZ in S1, the actual UW colors are set in LZSetup
public table PlayerObject_KnuxSuperAltPal
	0x600020, 0xC00040, 0xE04080
	0x802040, 0xE04060, 0xE060A0
	0xA04060, 0xE06080, 0xE080C0
	0xC06080, 0xE080A0, 0xE0A0E0
	0xE080A0, 0xE0A0C0, 0xE0C0E0
	0xE0A0C0, 0xE0C0E0, 0xE0E0E0
	0xE080A0, 0xE0A0C0, 0xE0C0E0
	0xC06080, 0xE080A0, 0xE0A0E0
	0xA04060, 0xE06080, 0xE080C0
	0x802040, 0xE04060, 0xE060A0
end table

public table PlayerObject_SonicPhysicsTable
	0x60000, 0x0C00, 0x1800, 0x0600, 0x008000, 0x0600, 0x68000, -0x40000
	0x30000, 0x0600, 0xC00,  0x0300, 0x004000, 0x0300, 0x38000, -0x20000
	0xA0000, 0x3000, 0x6000, 0x1800, 0x010000, 0x0600, 0x80000, -0x40000
	0x50000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0300, 0x38000, -0x20000
	0xC0000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0600, 0x68000, -0x40000
	0x60000, 0x0C00, 0x1800, 0x0600, 0x004000, 0x0300, 0x38000, -0x20000
	0xC0000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0600, 0x80000, -0x40000
	0x60000, 0x0C00, 0x1800, 0x0600, 0x004000, 0x0300, 0x38000, -0x20000
end table

public table PlayerObject_TailsPhysicsTable
	0x60000, 0x0C00, 0x1800, 0x0600, 0x008000, 0x0600, 0x68000, -0x40000 
	0x30000, 0x0600, 0x0C00, 0x0300, 0x004000, 0x0300, 0x38000, -0x20000 
	0xA0000, 0x3000, 0x6000, 0x1800, 0x010000, 0x0600, 0x80000, -0x40000 
	0x50000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0300, 0x38000, -0x20000 
	0xC0000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0600, 0x68000, -0x40000 
	0x60000, 0x0C00, 0x1800, 0x0600, 0x004000, 0x0300, 0x38000, -0x20000 
	0xC0000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0600, 0x80000, -0x40000 
	0x60000, 0x0C00, 0x1800, 0x0600, 0x004000, 0x0300, 0x38000, -0x20000
end table

public table PlayerObject_KnuxPhysicsTable
	0x60000, 0x0C00, 0x1800, 0x0600, 0x008000, 0x0600, 0x60000, -0x40000
	0x30000, 0x0600, 0x0C00, 0x0300, 0x004000, 0x0300, 0x30000, -0x20000
	0xA0000, 0x3000, 0x6000, 0x1800, 0x010000, 0x0600, 0x60000, -0x40000
	0x50000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0300, 0x30000, -0x20000
	0xC0000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0600, 0x60000, -0x40000
	0x60000, 0x0C00, 0x1800, 0x0600, 0x004000, 0x0300, 0x30000, -0x20000
	0xC0000, 0x1800, 0x3000, 0x0C00, 0x008000, 0x0600, 0x60000, -0x40000
	0x60000, 0x0C00, 0x1800, 0x0600, 0x008000, 0x0300, 0x30000, -0x20000
end table

// Initialize the character object for replay playback
function PlayerObject_InitReplay

	// Input entries start 2 indexes in
	PlayerObject_ReplayTablePos = 2
	PlayerObject_ReplayFrameCnt = 1

	// Reset each player's controls
	currentPlayer = 0
	while currentPlayer < playerCount
		GetTableValue(player[currentPlayer].xpos, 0, PlayerObject_ReplayTable)
		GetTableValue(player[currentPlayer].ypos, 1, PlayerObject_ReplayTable)
		player[currentPlayer].controlMode = -1
		player[currentPlayer].up = 0
		player[currentPlayer].down = 0
		player[currentPlayer].left = 0
		player[currentPlayer].right = 0
		player[currentPlayer].jumpPress = 0
		player[currentPlayer].jumpHold = 0
		player[currentPlayer].timer = 0
		currentPlayer++
	loop

	screen.cameraX = player[0].ixpos
	screen.cameraY = player[0].iypos

end function


// Give the player back their shield after invincibility
function PlayerObject_RestorePowerup

	switch player[currentPlayer].shield
	case SHIELD_NONE // No shield - set the shield slot to blank
		ResetObjectEntity(arrayPos0, TypeName[Blank Object], 0, 0, 0)
		break

	case SHIELD_NORMAL // Blue shield, restore it and also set its alpha properties
		ResetObjectEntity(arrayPos0, blueShieldType, 0, 0, 0)
		object[arrayPos0].priority = PRIORITY_ACTIVE
		object[arrayPos0].inkEffect = INK_ALPHA
		object[arrayPos0].alpha = 160
		break

	case SHIELD_BUBBLE // Bubble shield, restore it and set it to active
		ResetObjectEntity(arrayPos0, TypeName[Bubble Shield], 0, 0, 0)
		object[arrayPos0].priority = PRIORITY_ACTIVE
		break

	case SHIELD_FIRE // Flame shield, restore it and set it to active
		ResetObjectEntity(arrayPos0, TypeName[Fire Shield], 0, 0, 0)
		object[arrayPos0].priority = PRIORITY_ACTIVE
		break

	case SHIELD_LIGHTNING // Lightning shield, restore it and set it to active
		ResetObjectEntity(arrayPos0, TypeName[LightningShield], 0, 0, 0)
		object[arrayPos0].priority = PRIORITY_ACTIVE
		break

	case SHIELD_GOGGLES // LZ Googles, restore them and set them to draw right infront of the player
		ResetObjectEntity(arrayPos0, goggleType, 0, 0, 0)
		object[arrayPos0].priority = PRIORITY_ACTIVE
		object[arrayPos0].value0 = currentPlayer
		object[arrayPos0].drawOrder = DRAWORDER_PLAYER
		break

	end switch
end function


function PlayerObject_HandleSuperPalSonic

	if PlayerObject_SuperState == 1
		// Main Super state
		PlayerObject_SuperCounter2++
		if PlayerObject_SuperCounter2 >= 4
			PlayerObject_SuperCounter2 = 0
			PlayerObject_SuperCounter1 += 4
			if PlayerObject_SuperCounter1 >= 64
				PlayerObject_SuperCounter1 = 24
			end if
		end if
	else
		// Detransforming
		PlayerObject_SuperCounter2++
		if PlayerObject_SuperCounter2 >= 8
			PlayerObject_SuperCounter2 = 0
			PlayerObject_SuperCounter1 -= 4
			if PlayerObject_SuperCounter1 <= 0
				PlayerObject_SuperCounter1 = 0
				PlayerObject_SuperState = 0
			end if
			if PlayerObject_SuperCounter1 >= 24
				PlayerObject_SuperCounter1 = 24
			end if
		end if
	end if

	// Make Sonic's Super palette all flashy
	// Palette table 0 is normal palette, palette table 1 is underwater palette
	temp1 = PlayerObject_SuperCounter1
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperPal)
	SetPaletteEntry(0, 2, temp0)
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperAltPal)
	SetPaletteEntry(1, 2, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperPal)
	SetPaletteEntry(0, 3, temp0)
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperAltPal)
	SetPaletteEntry(1, 3, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperPal)
	SetPaletteEntry(0, 4, temp0)
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperAltPal)
	SetPaletteEntry(1, 4, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperPal)
	SetPaletteEntry(0, 5, temp0)
	GetTableValue(temp0, temp1, PlayerObject_SonicSuperAltPal)
	SetPaletteEntry(1, 5, temp0)

end function


function PlayerObject_HandleSuperPalTails

	if PlayerObject_SuperState == 1
		// Main Super state
		PlayerObject_SuperCounter2++
		if PlayerObject_SuperCounter2 >= 12
			PlayerObject_SuperCounter2 = 0
			PlayerObject_SuperCounter1 += 4
			if PlayerObject_SuperCounter1 >= 24
				PlayerObject_SuperCounter1 = 0
			end if
		end if
	else
		// Detransforming
		if PlayerObject_SuperCounter1 > 12
			FlipSign(PlayerObject_SuperCounter1)
			PlayerObject_SuperCounter1 += 24
		end if
		PlayerObject_SuperCounter2++
		if PlayerObject_SuperCounter2 >= 12
			PlayerObject_SuperCounter2 = 0
			PlayerObject_SuperCounter1 -= 4
			if PlayerObject_SuperCounter1 <= 0
				PlayerObject_SuperCounter1 = 0
				PlayerObject_SuperState = 0
			end if
		end if
	end if

	temp1 = PlayerObject_SuperCounter1
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperPal)
	SetPaletteEntry(0, 13, temp0)
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperAltPal)
	SetPaletteEntry(1, 13, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperPal)
	SetPaletteEntry(0, 17, temp0)
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperAltPal)
	SetPaletteEntry(1, 17, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperPal)
	SetPaletteEntry(0, 18, temp0)
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperAltPal)
	SetPaletteEntry(1, 18, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperPal)
	SetPaletteEntry(0, 16, temp0)
	GetTableValue(temp0, temp1, PlayerObject_TailsSuperAltPal)
	SetPaletteEntry(1, 16, temp0)

end function


function PlayerObject_HandleSuperPalKnux

	if PlayerObject_SuperState == 1
		// Main Super state
		PlayerObject_SuperCounter2++
		if PlayerObject_SuperCounter2 >= 12
			PlayerObject_SuperCounter2 = 9
			PlayerObject_SuperCounter1 += 3
			if PlayerObject_SuperCounter1 >= 30
				PlayerObject_SuperCounter2 = 0
				PlayerObject_SuperCounter1 = 0
			end if
		end if
	else
		// Detransforming
		if PlayerObject_SuperCounter1 > 15
			FlipSign(PlayerObject_SuperCounter1)
			PlayerObject_SuperCounter1 += 30
		end if
		PlayerObject_SuperCounter2++
		if PlayerObject_SuperCounter2 >= 12
			PlayerObject_SuperCounter2 = 0
			PlayerObject_SuperCounter1 -= 3
			if PlayerObject_SuperCounter1 <= 0
				PlayerObject_SuperCounter1 = 0
				PlayerObject_SuperState = 0
			end if
		end if
	end if

	temp1 = PlayerObject_SuperCounter1
	GetTableValue(temp0, temp1, PlayerObject_KnuxSuperPal)
	SetPaletteEntry(0, 26, temp0)
	GetTableValue(temp0, temp1, PlayerObject_KnuxSuperAltPal)
	SetPaletteEntry(1, 26, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_KnuxSuperPal)
	SetPaletteEntry(0, 27, temp0)
	GetTableValue(temp0, temp1, PlayerObject_KnuxSuperAltPal)
	SetPaletteEntry(1, 27, temp0)
	temp1++
	GetTableValue(temp0, temp1, PlayerObject_KnuxSuperPal)
	SetPaletteEntry(0, 28, temp0)
	GetTableValue(temp0, temp1, PlayerObject_KnuxSuperAltPal)
	SetPaletteEntry(1, 28, temp0)

end function


function PlayerObject_SwapPhysics

	switch stage.playerListPos
	case PlayerName[SONIC] // Sonic alone falls through to S&T
	case PlayerName[SONIC AND TAILS]
		temp0 = PlayerObject_SonicPhysicsTable
		break

	case PlayerName[TAILS]
		temp0 = PlayerObject_TailsPhysicsTable
		break

	case PlayerName[KNUCKLES]
		temp0 = PlayerObject_KnuxPhysicsTable
		break

	end switch

	temp1 = 0
	temp2 = 0

	if stage.state != STAGE_FROZEN
		// Is the player underwater?
		temp3 = object[currentPlayer].ypos
		temp3 >>= 16
		CheckGreater(temp3, stage.waterLevel)

		// Make sure the player isn't in debug mode
		temp4 = checkResult
		CheckNotEqual(object[currentPlayer].type, TypeName[Debug Mode])
		temp4 &= checkResult

		if temp4 == true
			SetBit(temp1, 0, 1)
			player[currentPlayer].gravityValue = 0x1000
		else
			player[currentPlayer].gravityValue = 0x3800
		end if

		if PlayerObject_SuperState == 1
			SetBit(temp1, 1, 1)
			temp2 = 2
		end if

		if player[currentPlayer].speedShoes > 0
			SetBit(temp1, 2, 1)
			temp2 = 1
		end if

		temp1 <<= 3
	end if

	// Set the physics values
	GetTableValue(player[currentPlayer].speedCap, temp1, temp0)
	temp1++
	GetTableValue(player[currentPlayer].acceleration, temp1, temp0)
	player[currentPlayer].deceleration = player[currentPlayer].acceleration
	player[currentPlayer].deceleration >>= temp2
	temp1++
	GetTableValue(player[currentPlayer].airAcceleration, temp1, temp0)
	temp1++
	GetTableValue(object[currentPlayer].value24, temp1, temp0)
	temp1++
	GetTableValue(player[currentPlayer].baseSpeed, temp1, temp0)
	temp1++
	GetTableValue(player[currentPlayer].rollingDecelerationA, temp1, temp0)
	temp1++
	GetTableValue(player[currentPlayer].jumpVelocity, temp1, temp0)
	temp1++
	GetTableValue(player[currentPlayer].jumpStrength, temp1, temp0)
end function


function PlayerObject_ProcessSuperForms

	// Do the fancy palette stuff if Super
	if PlayerObject_SuperState != 0
		switch stage.playerListPos
		case PlayerName[SONIC] // Sonic alone falls through to S&T
		case PlayerName[SONIC AND TAILS]
			CallFunction(PlayerObject_HandleSuperPalSonic)
		        object[0].value32 = PlayerObject_HyperDoubleJump
			break

		case PlayerName[TAILS]
			CallFunction(PlayerObject_HandleSuperPalTails)
			break

		case PlayerName[KNUCKLES]
			CallFunction(PlayerObject_HandleSuperPalKnux)
			break

		end switch
	end if

	// Main Super state
	if PlayerObject_SuperState == 1
		player.invincible = 60

		PlayerObject_SuperRingDecFrames++

		// If a second has passed since the last ring loss...
		if PlayerObject_SuperRingDecFrames == 60
			// ...then take another ring from the player
			PlayerObject_SuperRingDecFrames = 0
			player.rings--

			// If the player has no more rings, make them untransform
			if player.rings == 0
				PlayerObject_SuperState = 2
			end if
		end if
	end if

	// Untransformation state
	if PlayerObject_SuperState == 2
		// If Sonic, go back to normal Sonic sprites
		if stage.playerListPos == PlayerName[SONIC]
			LoadAnimation("Sonic.ani")
		end if

		// Is the invincibility music playing?
		if music.currentTrack == 2
			PlayMusic(0)
		end if

		// Reset the player's invincibility
		player.invincible = 0

		// If the player isn't already dead then give them back their shield
		if player.state != PlayerObject_Death
			if player.state != PlayerObject_Drown

				currentPlayer = player.entityPos
				arrayPos0 = currentPlayer
				arrayPos0 += playerCount
				CallFunction(PlayerObject_RestorePowerup)

			end if
		end if

		PlayerObject_SuperState = 3
		CallFunction(PlayerObject_SwapPhysics)
	end if

end function


// Used by badniks to check for if the player should get hit
function PlayerObject_CheckHit

	CheckEqual(player[currentPlayer].animation, ANI_JUMPING)
	temp0 = checkResult
	CheckEqual(player[currentPlayer].animation, ANI_SPINDASH)
	temp0 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_GLIDING)
	temp0 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_GLIDING_STOP)
	temp0 |= checkResult
	CheckNotEqual(player[currentPlayer].invincible, 0)
	temp0 |= checkResult

	CheckEqual(player[currentPlayer].animation, ANI_FLYING)
	temp1 = checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLYINGTIRED)
	temp1 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLY_LIFT_UP)
	temp1 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLY_LIFT_DOWN)
	temp1 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLY_LIFT_TIRED)
	temp1 |= checkResult

	if temp1 == true
		CheckGreater(player[currentPlayer].ypos, object.ypos)
		temp0 |= checkResult
	end if

	if temp0 == true
		FlipSign(player[currentPlayer].xvel)
		player[currentPlayer].xvel >>= 1
		player[currentPlayer].speed = player[currentPlayer].xvel
		FlipSign(player[currentPlayer].yvel)
		player[currentPlayer].yvel >>= 1

		if player[currentPlayer].animation == ANI_GLIDING
			player[currentPlayer].animation = ANI_GLIDING_DROP
			player[currentPlayer].state = PlayerObject_KnuxGlideDrop
		end if

		checkResult = true
	else
		if player[currentPlayer].state != PlayerObject_Death
			if player[currentPlayer].invincible == 0
				if player[currentPlayer].invulnerable == 0

					player[currentPlayer].state = PlayerObject_Hurt

					if player[currentPlayer].xpos > object.xpos
						player[currentPlayer].speed = 0x20000
					else
						player[currentPlayer].speed = -0x20000
					end if

				end if
			end if
		end if

		checkResult = false
	end if

end function


// Used by badniks to check if the badnik should be destroyed
function PlayerObject_BadnikBreak

	CheckEqual(player[currentPlayer].animation, ANI_JUMPING)
	temp0 = checkResult
	CheckEqual(player[currentPlayer].animation, ANI_SPINDASH)
	temp0 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_GLIDING)
	temp0 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_GLIDING_STOP)
	temp0 |= checkResult
	CheckNotEqual(player[currentPlayer].invincible, 0)
	temp0 |= checkResult

	CheckEqual(player[currentPlayer].animation, ANI_FLYING)
	temp1 = checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLYINGTIRED)
	temp1 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLY_LIFT_UP)
	temp1 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLY_LIFT_DOWN)
	temp1 |= checkResult
	CheckEqual(player[currentPlayer].animation, ANI_FLY_LIFT_TIRED)
	temp1 |= checkResult

	if temp1 == true
		CheckGreater(player[currentPlayer].ypos, object.ypos)
		temp0 |= checkResult
	end if

	if temp0 == true
		ResetObjectEntity(object.entityPos, TypeName[Blank Object], 0, object.xpos, object.ypos)
		Rand(checkResult, 32)
		if checkResult > 15
			CreateTempObject(animalType1, TypeName[Blank Object], object.xpos, object.ypos)
		else
			CreateTempObject(animalType2, TypeName[Blank Object], object.xpos, object.ypos)
		end if
		object[tempObjectPos].priority = PRIORITY_UNKNOWN
		CreateTempObject(TypeName[Smoke Puff], 0, object.xpos, object.ypos)
		object[tempObjectPos].drawOrder = 4
		CreateTempObject(TypeName[Object Score], player[currentPlayer].enemyChainCounter, object.xpos, object.ypos)
		object[tempObjectPos].drawOrder = 4
		PlaySfx(SfxName[Destroy], 0)

		if player[currentPlayer].yvel > 0
			if player[currentPlayer].ypos >= object.ypos
				player[currentPlayer].yvel -= 0x10000
			else
				player[currentPlayer].yvel += player[currentPlayer].gravityValue
				player[currentPlayer].yvel += player[currentPlayer].gravityValue
				FlipSign(player[currentPlayer].yvel)
			end if
		else
			player[currentPlayer].yvel += 0x10000
		end if

		// If not in competition mode, then give P1 the score
		if options.competition == false
			temp0 = currentPlayer
			currentPlayer = 0
		end if

		switch player[currentPlayer].enemyChainCounter
		case 0
			player.score += 100
			break

		case 1
			player.score += 200
			break

		case 2
			player.score += 500
			break

		case 3 // Fall through
		case 4
		case 5
		case 6
		case 7
		case 8
		case 9
		case 10
		case 11
		case 12
		case 13
		case 14
			player.score += 1000
			break

		case 15
			player.score += 10000
			break

		end switch

		if player[currentPlayer].enemyChainCounter < 15
			player[currentPlayer].enemyChainCounter++
		end if

		// Move back to whatever player this was originally intended for
		if options.competition == false
			currentPlayer = temp0
		end if
	else
		// If the player doesn't have any hitboxes active, then damage (or kill) them
		if player[currentPlayer].state != PlayerObject_Death
			if player[currentPlayer].invincible == 0
				if player[currentPlayer].invulnerable == 0

					player[currentPlayer].state = PlayerObject_Hurt
					if player[currentPlayer].xpos > object.xpos
						player[currentPlayer].speed = 0x20000
					else
						player[currentPlayer].speed = -0x20000
					end if

				end if
			end if
		end if
	end if
	
end function


// Ouch!
function PlayerObject_Hit

	if player[currentPlayer].state != PlayerObject_Death
		arrayPos0 = object[currentPlayer].entityPos
		arrayPos0 += playerCount

		if player[currentPlayer].invincible == 0
			if player[currentPlayer].invulnerable == 0

				player[currentPlayer].state = PlayerObject_Hurt
				if player[currentPlayer].xpos > object.xpos
					player[currentPlayer].speed = 0x20000
				else
					player[currentPlayer].speed = -0x20000
				end if

			end if
		end if
	end if

end function


// Used by things like MZ lava and other fire-based obstacles
function PlayerObject_FireHit

	// Do not hurt the player if they have a flame shield
	if player[currentPlayer].shield != SHIELD_FIRE
		if player[currentPlayer].state != PlayerObject_Death

			arrayPos0 = player[currentPlayer].entityPos
			arrayPos0 += playerCount

			if player[currentPlayer].invincible == 0

				if player[currentPlayer].invulnerable == 0
					player[currentPlayer].state = PlayerObject_Hurt
					if player[currentPlayer].xpos > object.xpos
						player[currentPlayer].speed = 0x20000
					else
						player[currentPlayer].speed = -0x20000
					end if
				end if

			end if

		end if
	end if

end function


// Used by things like SBZ lightning and other lightning-based obstacles
function PlayerObject_LightningHit

	// Do not hurt the player if they have a lightning shield
	if player[currentPlayer].shield != SHIELD_LIGHTNING
		if player[currentPlayer].state != PlayerObject_Death

			// (Player shouldn't have a lighting shield underwater so no other check is needed here)

			arrayPos0 = player[currentPlayer].entityPos
			arrayPos0 += playerCount

			if player[currentPlayer].invincible == 0
				if player[currentPlayer].invulnerable == 0

					player[currentPlayer].state = PlayerObject_Hurt

					if player[currentPlayer].xpos > object.xpos
						player[currentPlayer].speed = 0x20000
					else
						player[currentPlayer].speed = -0x20000
					end if

				end if
			end if
		end if
	end if
end function


// Used by badnik projectiles when on player collision
function PlayerObject_ProjectileHit

	if player[currentPlayer].shield > SHIELD_NORMAL // Reflect it if the player has an elemental shield
		temp0 = player[currentPlayer].xpos
		temp0 -= object.xpos
		temp1 = player[currentPlayer].ypos
		temp1 -= object.ypos
		ATan2(temp2, temp0, temp1)
		Sin256(temp0, temp2)
		Cos256(temp1, temp2)
		object.xvel = temp1
		object.xvel *= -0x800
		object.yvel = temp0
		object.yvel *= -0x800
	else

		if player[currentPlayer].state != PlayerObject_Death
			arrayPos0 = player[currentPlayer].entityPos
			arrayPos0 += playerCount

			if player[currentPlayer].invincible == 0
				if player[currentPlayer].invulnerable == 0
					player[currentPlayer].state = PlayerObject_Hurt

					if player[currentPlayer].xpos > object.xpos
						player[currentPlayer].speed = 0x20000
					else
						player[currentPlayer].speed = -0x20000
					end if
				end if
			end if
		end if

	end if

end function


// Used by spikes upon player collision
function PlayerObject_SpikeHit

	if object[currentPlayer].state != PlayerObject_Death
		arrayPos0 = player[currentPlayer].entityPos
		arrayPos0 += playerCount

		if player[currentPlayer].invincible == 0
			if player[currentPlayer].state != PlayerObject_Hurt
				if player[currentPlayer].state != PlayerObject_Knockback

					// Check for spike bug if player is invulnerable
					temp0 = options.spikeBehavior
					CheckEqual(player.invulnerable[currentPlayer], 0)
					temp0 |= checkResult

					if temp0 == true

						if player[currentPlayer].invulnerable == 0
							player[currentPlayer].invulnerable = 2
						end if

						player[currentPlayer].state = PlayerObject_Hurt
						if player[currentPlayer].xpos > object.xpos
							player[currentPlayer].speed = 0x20000
						else
							player[currentPlayer].speed = -0x20000
						end if

					end if
				end if
			end if
		end if
	end if

end function


// Used by other objects to kill currentPlayer, not to be confused with PlayerObject_Death
function PlayerObject_Kill

	// Play the corresponding SFX
	PlaySfx(SfxName[Hurt], 0)

	// Reset player stuff
	player[currentPlayer].speed = 0
	player[currentPlayer].xvel = 0
	player[currentPlayer].yvel = -0x68000
	player[currentPlayer].state = PlayerObject_Death
	player[currentPlayer].animation = ANI_DYING
	player[currentPlayer].tileCollisions = false
	player[currentPlayer].interaction = false
	player[currentPlayer].invulnerable = 0
	player[currentPlayer].visible = true
	player[currentPlayer].sortedDrawOrder = 6

	// Is this the main character?
	if currentPlayer == 0
		player[currentPlayer].priority = PRIORITY_ACTIVE_PAUSED

		// Disable P2 (if it exists)
		if player[1].type == TypeName[Player 2 Object]
			player[1].priority = PRIORITY_ACTIVE_PAUSED
		end if

		// Stop the game flow
		screen.cameraEnabled = false
		stage.state = 3
	end if

	arrayPos0 = currentPlayer
	arrayPos0 += playerCount

	if object[arrayPos0].type == invincibilityType
		object[arrayPos0].propertyValue = 3
	end if

	// Remove the player's shield
	object[arrayPos0].type = TypeName[Blank Object]
	player[currentPlayer].shield = SHIELD_NONE
end function


// Handles important stuff called regardless of state
function PlayerObject_ProcessPlayer

	if options.attractMode == false
		if player.controlMode == false
			CheckTouchRect(0, 96, screen.xcenter, screen.ysize)

			if checkResult > -1
				arrayPos0 = checkResult

				temp0 = touchscreen[arrayPos0].xpos
				temp0 -= saveRAM[39]

				temp1 = touchscreen[arrayPos0].ypos
				temp1 -= saveRAM[40]

				ATan2(temp2, temp0, temp1)

				temp2 += 32
				temp2 &= 255
				temp2 >>= 6

				switch temp2
				case 0
					inputDown.right = true
					break

				case 1
					inputDown.down = true
					break

				case 2
					inputDown.left = true
					break

				case 3
					inputDown.up = true
					break

				end switch
			end if


			CheckTouchRect(screen.xcenter, 96, screen.xsize, 240)

			if checkResult > -1
				inputDown.buttonA = true
			end if

			if touchJump == false
				inputPress.buttonA |= inputDown.buttonA
			end if

			touchJump = inputDown.buttonA

			if stage.debugMode == true
				CheckTouchRect(0, 0, 112, 56)

				if checkResult > -1
					inputDown.buttonB = true
				end if

				if touchDebug == false
					inputPress.buttonB |= inputDown.buttonB
				end if

				touchDebug = inputDown.buttonB
			end if

			CheckTouchRect(240, 0, screen.xsize, 40)

			if checkResult > -1
				// Pause the game
				PlaySfx(SfxName[Menu Back], 0)
				StopSfx(SfxName[Flying])
				StopSfx(SfxName[Jump])
				engine.state = 5
			end if

			if inputPress.start == true
				// Pause the game
				PlaySfx(SfxName[Menu Back], 0)
				StopSfx(SfxName[Flying])
				StopSfx(SfxName[Jump])
				engine.state = 5
			end if
		end if

		ProcessObjectControl()
	else
		// In attract mode

		if credits.screen == false
			CheckTouchRect(0, 0, screen.xsize, screen.ysize)

			if inputPress.start == true
				checkResult = false
			end if

			if checkResult > -1
				if PlayerObject_ReplayLength > 1
					PlayerObject_ReplayLength = 1
				end if
			end if

			if inputPress.start == true
				if PlayerObject_ReplayLength > 1
					PlayerObject_ReplayLength = 1
				end if
			end if
		end if

		if player.controlMode == false
			PlayerObject_ReplayFrameCnt--

			if PlayerObject_ReplayFrameCnt < 1
				if PlayerObject_ReplayTablePos < PlayerObject_ReplaySize
					// Get and set the player inputs for this frame based on the replay table
					GetTableValue(temp0, PlayerObject_ReplayTablePos, PlayerObject_ReplayTable)
					GetBit(player.up, temp0, 0)
					GetBit(player.down, temp0, 1)
					GetBit(player.left, temp0, 2)
					GetBit(player.right, temp0, 3)
					GetBit(player.jumpPress, temp0, 4)
					GetBit(player.jumpHold, temp0, 5)
					PlayerObject_ReplayTablePos++
					GetTableValue(PlayerObject_ReplayFrameCnt, PlayerObject_ReplayTablePos, PlayerObject_ReplayTable)
					PlayerObject_ReplayTablePos++
				end if
			else
				if player.jumpPress == true
					player.jumpPress = false
				end if
			end if

			// Update replay timer
			if PlayerObject_ReplayLength > 0
				PlayerObject_ReplayLength--

				if PlayerObject_ReplayLength < 1 // Replay over?

					// Start fading out
					ResetObjectEntity(11, TypeName[Title Card], 0, 0, 0)
					object[11].state = 8
					object[11].priority = PRIORITY_ACTIVE
					object[11].drawOrder = 6
					player.invincible = 80
					screen.cameraEnabled = false
				end if
			end if
		end if
	end if

	// Update speed shoes
	if player.speedShoes > 0
		player.speedShoes-- // Decrease the timer

		// Ran out of speed shoes?
		if player.speedShoes < 1
			// Reset player speed
			currentPlayer = player.entityPos
			CallFunction(PlayerObject_SwapPhysics)

			// SlowDownMusic is normally set by the stage's setup script, but if it's not set then don't call it
			if SlowDownMusic != 0
				CallFunction(SlowDownMusic)
			end if

			player.speedShoes = 0 // Reset the speed shoes counter, shouldn't be needed but it doesn't hurt
		end if
	end if

	// Update invulnerability
	if player.state != PlayerObject_Knockback // Don't update during knockback
		if player.invulnerable > 0			  // Only update if invulnerable, of course
			player.invulnerable--

			GetBit(temp0, player.invulnerable, 2) // Get the 3rd bit in the variable
			if temp0 == true // Determine whether or not to show the player based on that bit
				player.visible = false
			else
				player.visible = true
			end if
		end if
	end if

	// Update invincibility
	if player.invincible > 0
		player.invincible--

		// Out of invincibility?
		if player.invincible == 0
			// If currently playing invincibility music, go back to normal stage music
			if music.currentTrack == 2
				PlayMusic(0)
			end if

			// If the next object in an invincibility effect object, remove it and restore the shield the player had, if any
			if object[+playerCount].type == invincibilityType
				currentPlayer = player.entityPos
				arrayPos0 = currentPlayer
				arrayPos0 += playerCount
				CallFunction(PlayerObject_RestorePowerup)
			end if
		end if
	end if

	// Update camera panning while looking up/down
	if player.state != PlayerObject_LookingUp
		if player.state != PlayerObject_Crouching
			if player.lookPosY > 0
				player.lookPosY -= 2
			end if

			if player.lookPosY < 0
				player.lookPosY += 2
			end if
		end if
	end if

	// Update camera lock
	if camera.lockTimer > 0
		camera.lockTimer--

		if camera.lockTimer == 0
			screen.cameraStyle = 0 // "Free" the camera
		end if
	end if

	if player.state != PlayerObject_HandleTailsFlight
		if player.flightVelocity != 0
			StopSfx(SfxName[Flying])
			StopSfx(SfxName[Tired])
			player.flightVelocity = 0
		end if
	end if

end function


// Handles animation speed when rolling
function PlayerObject_HandleRollAnimSpd

	if player.character == PLAYER_TAILS_ALONE
		// Tails gets a constant anim speed of 120
		player.CustomRollAnimSpeed = 120
	else
		// S&K's animation speeds are dependant on their speed
		player.CustomRollAnimSpeed = player.speed

		// Get the absolute value of their speed
		if player.CustomRollAnimSpeed < 0
			FlipSign(player.CustomRollAnimSpeed)
		end if

		// Do some maths to get the final speed
		player.CustomRollAnimSpeed *= 240
		player.CustomRollAnimSpeed /= 0x60000
		player.CustomRollAnimSpeed += 48
	end if

end function


// Handles walking animation speed
// All characters share the same routine
function PlayerObject_HandleWalkAnimSpd

	player.animationSpeed = player.speed

	// Get the absolute value
	if player.animationSpeed < 0
		FlipSign(player.animationSpeed)
	end if

	// Do some maths
	player.animationSpeed *= 60
	player.animationSpeed /= 0x60000
	player.animationSpeed += 20
	
end function


// Handles running animation speed
// Also shared between characters
function PlayerObject_HandleRunAnimSpd

	player.animationSpeed = player.speed

	// Absolute value
	if player.animationSpeed < 0
		FlipSign(player.animationSpeed)
	end if

	// Do some (admittedly less complex) maths to get the final speed
	player.animationSpeed *= 80
	player.animationSpeed /= 0x60000

end function


// Handles movement based on things like angles and etc.
function PlayerObject_HandleMovement
	if player.controlLock > 0
		player.controlLock--
		Sin256(temp0, player.angle)
		temp0 *= 0x2000
		temp0 >>= 8
		player.speed += temp0
	else
		if player.left == true
			temp0 = player.speedCap
			FlipSign(temp0)
			if player.speed > temp0
				if player.speed > 0
					if player.collisionMode == CMODE_FLOOR
						if player.speed > 0x40000
							player.skidding = 16
						end if
					end if
					if player.speed < player.baseSpeed
						player.speed = player.baseSpeed
						FlipSign(player.speed)
						player.skidding = 0
					else
						player.speed -= player.baseSpeed
					end if
				else
					player.speed -= player.acceleration
					player.skidding = 0
				end if
			end if

			if player.speed <= 0
				player.direction = FACING_LEFT
			end if
		end if

		if player.right == true
			if player.speed < player.speedCap
				if player.speed < 0
					if player.collisionMode == CMODE_FLOOR
						if player.speed < -0x40000
							player.skidding = 16
						end if
					end if
					temp0 = player.baseSpeed
					FlipSign(temp0)
					if player.speed > temp0
						player.speed = player.baseSpeed
						player.skidding = 0
					else
						player.speed += player.baseSpeed
					end if
				else
					player.speed += player.acceleration
					player.skidding = 0
				end if
			end if
			if player.speed >= 0
				player.direction = FACING_RIGHT
			end if
		end if
		temp0 = player.left
		temp0 |= player.right
		if temp0 == false
			if player.speed > 0
				player.speed -= player.deceleration
				if player.speed < 0
					player.speed = 0
				end if
			else
				player.speed += player.deceleration
				if player.speed > 0
					player.speed = 0
				end if
			end if
			if player.speed > 0x2000
				Sin256(temp0, player.angle)
				temp0 *= 0x2000
				temp0 >>= 8
				player.speed += temp0
			end if
			if player.speed < -0x2000
				Sin256(temp0, player.angle)
				temp0 *= 0x2000
				temp0 >>= 8
				player.speed += temp0
			end if
			if player.angle > 192
				if player.angle < 228
					if player.speed > -0x10000
						if player.speed < 0x10000
							player.controlLock = 30
						end if
					end if
				end if
			end if
			if player.angle > 28
				if player.angle < 64
					if player.speed > -0x10000
						if player.speed < 0x10000
							player.controlLock = 30
						end if
					end if
				end if
			end if
		else
			Sin256(temp0, player.angle)
			temp0 *= 0x2000
			temp0 >>= 8
			player.speed += temp0
			if player.right == true
				if player.left == false
					if player.angle > 192
						if player.angle < 228
							if player.speed < 0x28000
								if player.speed > -0x20000
									player.controlLock = 30
								end if
							end if
						end if
					end if
				end if
			else
				if player.left == true
					if player.angle > 28
						if player.angle < 64
							if player.speed > -0x28000
								if player.speed < 0x20000
									player.controlLock = 30
								end if
							end if
						end if
					end if
				end if
			end if
		end if
		if options.speedCap == true
			if player.left == true
				temp0 = player.speedCap
				FlipSign(temp0)
				if player.speed < temp0
					player.speed = temp0
				end if
			end if
			if player.right == true
				if player.speed > player.speedCap
					player.speed = player.speedCap
				end if
			end if
		end if
	end if
	switch player.collisionMode
	case 1
		if player.angle <= 192
			if player.speed > -0x20000
				if player.speed < 0x20000
					player.gravity = GRAVITY_AIR
					player.angle = 0
					player.collisionMode = CMODE_FLOOR
					player.speed = player.xvel
				end if
			end if
		end if
		break
	case 2
		if player.speed > -0x20000
			if player.speed < 0x20000
				player.gravity = GRAVITY_AIR
				player.angle = 0
				player.collisionMode = CMODE_FLOOR
				player.speed = player.xvel
			end if
		end if
		break
	case 3
		if player.angle >= 64
			if player.speed > -0x20000
				if player.speed < 0x20000
					player.gravity = GRAVITY_AIR
					player.angle = 0
					player.collisionMode = CMODE_FLOOR
					player.speed = player.xvel
				end if
			end if
		end if
		break
	end switch
end function


// Handles player movement through the air
function PlayerObject_HandleAirAcceleration

	// (For when the character is rising from a jump)
	if player.yvel > -0x40000
		if player.yvel < 0
			// Limit max velocity
			temp0 = player.speed
			temp0 >>= 5
			player.speed -= temp0
		end if
	end if


	// Update air control

	// Moving left?
	temp0 = player.speedCap
	FlipSign(temp0)
	if player.speed > temp0
		if player.left == true
			// Player hasn't reached the speed cap yet, allow them to continue accelerating
			player.speed -= player.airAcceleration
			player.direction = FACING_LEFT
		end if
	else
		// Player has hit speed cap, don't allow them to accelerate further
		if player.left == true
			player.direction = FACING_LEFT
		end if
	end if


	// Same as above but for moving right
	if player.speed < player.speedCap
		// Player hasn't hit speed cap yet, allow them to get faster
		if player.right == true
			player.speed += player.airAcceleration
			player.direction = FACING_RIGHT
		end if
	else
		// Player has reached speed cap, stop speeding them up
		if player.right == true
			player.direction = FACING_RIGHT
		end if
	end if

	// Limit player speed if air speed cap is turned on
	if options.airSpeedCap == true
		// Player is trying to left, use negative version of speed cap
		if player.left == true
			temp0 = player.speedCap
			FlipSign(temp0)
			if player.speed < temp0
				player.speed = temp0
			end if
		end if

		// Player is trying to go right, use normal version of speed cap
		if player.right == true
			if player.speed > player.speedCap
				player.speed = player.speedCap
			end if
		end if
	end if
end function


// Handles player rolling on ground. Not to be confused for PlayerObject_HandleRoll
function PlayerObject_HandleRolling

	if player.right == true
		if player.speed < 0
			player.speed += player.rollingDecelerationP
		end if
	end if

	if player.left == true
		if player.speed > 0
			player.speed -= player.rollingDecelerationP
		end if
	end if

	temp1 = player.speed

	if player.speed > 0
		// Player is moving right

		player.speed -= player.rollingDecelerationA
		Sin256(temp0, player.angle)

		if temp0 > 0
			Sin256(temp0, player.angle)
			temp0 *= 0x5000
		else
			Sin256(temp0, player.angle)
			temp0 *= 0x1400
		end if

		temp0 >>= 8
		player.speed += temp0
		if player.speed > 0x120000
			player.speed = 0x120000
		end if
	else
		// Player is moving left

		player.speed += player.rollingDecelerationA
		Sin256(temp0, player.angle)

		if temp0 < 0
			Sin256(temp0, player.angle)
			temp0 *= 0x5000
		else
			Sin256(temp0, player.angle)
			temp0 *= 0x1400
		end if

		temp0 >>= 8
		player.speed += temp0
		if player.speed < -0x120000
			player.speed = -0x120000
		end if
	end if

	switch player.collisionMode
	case 0
	case 2
		if temp1 > 0
			if player.speed < 0

				player.speed = 0
				player.state = PlayerObject_HandleGround

			end if
		else
			if player.speed > 0

				player.speed = 0
				player.state = PlayerObject_HandleGround

			end if
		end if
		break

	case 1
		if player.angle < 193
			if temp1 > 0
				if player.speed < 0x20000
					player.gravity = GRAVITY_AIR
					player.xvel = 0
					player.speed = 0
				end if
			end if
		end if
		break

	case 3
		if player.angle > 63
			if temp1 < 0
				if player.speed > -0x20000
					player.gravity = GRAVITY_AIR
					player.xvel = 0
					player.speed = 0
				end if
			end if
		end if
		break

	end switch

end function

// Handles air movement for things like the player jumping and running off an incline
function PlayerObject_HandleAirMovement

	player.scrollTracking = true

	player.yvel += player.gravityValue

	if player.yvel < player.jumpStrength
		if player.jumpHold == false
			if player.timer > 0
				// Limit maximum velocity
				player.yvel = player.jumpStrength
				temp0 = player.speed
				temp0 >>= 5
				player.speed -= temp0
			end if
		end if
	end if

	player.xvel = player.speed

	if player.rotation < 256
		if player.rotation > 0
			player.rotation -= 4
		else
			player.rotation = 0
		end if
	else
		if player.rotation < 512
			player.rotation += 4
		else
			player.rotation = 0
		end if
	end if

	player.collisionMode = CMODE_FLOOR

	if player.animation == ANI_JUMPING
		player.animationSpeed = player.CustomRollAnimSpeed
	end if
end function


// Set various player values
function PlayerObject_ResetOnFloor

	player.scrollTracking = 0

	Cos256(temp0, player.angle)

	temp0 *= player.speed
	temp0 >>= 8

	player.xvel = temp0

	Sin256(temp0, player.angle)

	temp0 *= player.speed
	temp0 >>= 8

	player.yvel = temp0

end function


// Sometimes also set as the player's spindash function based on their settings
function PlayerObject_StartJump

	temp1 = false

	if player.collisionMode == CMODE_FLOOR
		// Check if there's a roof right above the player

		temp6 = player.xpos
		temp7 = player.ypos
		temp0 = player.collisionTop
		temp0 -= 2

		ObjectTileCollision(CSIDE_ROOF, 0, temp0, player.collisionPlane)

		temp1 = checkResult

		player.xpos = temp6
		player.ypos = temp7

		temp0 = player.collisionBottom

		if player.animation != ANI_JUMPING
			player.iypos -= player.jumpOffset
			temp0 += player.jumpOffset
		end if

		ObjectTileCollision(CSIDE_FLOOR, 0, temp0, player.collisionPlane)
	end if

	if temp1 == false
		// No roof - start jumping
		player.controlLock = 0

		player.gravity = GRAVITY_AIR

		temp1 = player.jumpVelocity
		temp1 += player.gravityValue

		Sin256(player.xvel, player.angle)
		player.xvel *= temp1

		Cos256(temp0, player.angle)
		temp0 *= player.speed

		player.xvel += temp0
		player.xvel >>= 8

		Sin256(player.yvel, player.angle)
		player.yvel *= player.speed

		Cos256(temp0, player.angle)

		temp0 *= temp1

		player.yvel -= temp0
		player.yvel >>= 8

		player.speed = player.xvel

		player.scrollTracking = true
		player.animation = ANI_JUMPING

		player.angle = 0

		player.collisionMode = CMODE_FLOOR
		player.timer = 1

		// Set initial animation speed
		CallFunction(PlayerObject_HandleRollAnimSpd)

		// Set the player's state to one depending if they were previously rolling or not
		if player.state == PlayerObject_HandleRoll
			player.state = PlayerObject_RollingJump
		else
			player.state = PlayerObject_HandleAir
		end if

		PlaySfx(SfxName[Jump], 0)

		player.collisionEnabled = true

		// Allow the player to use midair abilities
		player.doubleJumpState = 1
	end if

end function


// Only starts the spindash, the actual spindash state is PlayerObject_HandleSpindash
function PlayerObject_StartSpindash

	player.state = PlayerObject_HandleSpindash

	player.animation = ANI_SPINDASH

	player.abilityTimer = 0

	PlaySfx(SfxName[Charge], 0)

	// Create dust puff object
	CreateTempObject(TypeName[Dust Puff], player.entityPos, player.xpos, player.ypos)
	object[tempObjectPos].iypos = player.collisionBottom
	object[tempObjectPos].ypos += player.ypos
	object[tempObjectPos].frame = 4
	object[tempObjectPos].drawOrder = 4
	object[tempObjectPos].direction = player.direction
	
end function


// Temporary state for when the player is in their transform animation
function PlayerObject_Transforming

	player.collisionEnabled = true

	// Delay is normally 24 frames, set in the transform script rather than here
	player.timer--

	if player.timer == 0
		player.state = PlayerObject_HandleAir
		player.animation = ANI_WALKING
	end if

end function


// Function that transforms the player
function PlayerObject_SuperTransform

	// Only allow the player to transform during the actual level, not when results are showing or other blocking elements
	if stage.timeEnabled == true

		// Spawn the sparkly super trail object
		arrayPos0 = currentPlayer
		arrayPos0 += playerCount
		ResetObjectEntity(arrayPos0, TypeName[Invincibility], 0, player[0].xpos, player[0].ypos)
		object[arrayPos0].priority = PRIORITY_ACTIVE
		PlaySfx(SfxName[Transform], 0)
		switch saved.ssmusic
				case 0
					PlayMusic(7)
					break
				case 1
					PlayMusic(7)
					break
				case 2
					PlayMusic(7)
					break
				case 3
					PlayMusic(7)
					break
			end switch    
		end if        
		PlayerObject_SuperState = 1
		player[currentPlayer].invincible = 60
		player[currentPlayer].invulnerable = 0
		player[currentPlayer].visible = true

		CallFunction(PlayerObject_SwapPhysics)

		// Load Super Sonic sprites if Sonic
		if stage.playerListPos == PlayerName[SONIC]
			LoadAnimation("SuperSonic.ani")
		end if

		player[currentPlayer].state = PlayerObject_Transforming
		player[currentPlayer].collisionEnabled = true
		player[currentPlayer].animation = ANI_SUPER_TRANSFORM
		player[currentPlayer].timer = 24
		
	end if

end function


// When the player is falling from a bubble bounce
function PlayerObject_BubbleBounce

	CallFunction(PlayerObject_HandleAirAcceleration)

	// Check if the player should stop bubble-bouncing

	// Does the player no longer have the bubble shield?
	CheckNotEqual(player.shield, SHIELD_BUBBLE)
	temp0 = checkResult

	// Is the player invincible?
	CheckNotEqual(player.invincible, 0)
	temp0 |= checkResult

	// Is the player Super?
	CheckEqual(PlayerObject_SuperState, 1)
	temp0 |= checkResult

	if temp0 == true
		// At least one of the checks came back true, go back to normal air falling
		player.state = PlayerObject_HandleAir
	else
		if player.gravity == GRAVITY_AIR
			CallFunction(PlayerObject_HandleAirMovement)
		else
			// Allow the player to bounce again after rebound
			player.doubleJumpState = 1

			player.gravity = GRAVITY_AIR

			// Underwater?
			if player.jumpVelocity == 0x38000
				temp1 = -0x40000
			else
				temp1 = -0x78000
			end if

			temp1 += player.gravityValue

			Sin256(player.xvel, player.angle)
			player.xvel *= temp1

			Cos256(temp0, player.angle)
			temp0 *= player.speed

			player.xvel += temp0
			player.xvel >>= 8

			Sin256(player.yvel, player.angle)
			player.yvel *= player.speed

			Cos256(temp0, player.angle)
			temp0 *= temp1

			player.yvel = temp0
			player.yvel >>= 8

			player.speed = player.xvel

			player.scrollTracking = true
			player.animation = ANI_JUMPING

			player.angle = 0

			player.collisionMode = CMODE_FLOOR

			player.timer = 1

			CallFunction(PlayerObject_HandleRollAnimSpd)

			player.state = PlayerObject_HandleAir

			PlaySfx(SfxName[Bubble Bounce], 0)

			// Set the bubble shield state to 4
			object[+playerCount].state = 4
		end if
	end if

end function


// Sonic's ability
function PlayerObject_HandleShieldAbilities

	// Only act if the jump button is pressed
	if player.jumpPress == true
		// Following checks are to see if Sonic should transform instead of doing arial abilities

		// Each bit represents an emerald's collected state - 0b01111111 is 7 1's, AKA 7 emeralds
		CheckEqual(specialStage.emeralds, 0b01111111)
		temp0 = checkResult

		// Player needs to have at least 50 rings
		CheckGreater(player.rings, 49)
		temp0 &= checkResult

		// Player can't already be Super, of course
		CheckNotEqual(PlayerObject_SuperState, 1)
		temp0 &= checkResult

		// And don't let the player transform if the level is over, either
		CheckNotEqual(object[30].type, TypeName[Act Finish])
		temp0 &= checkResult

		if temp0 == true
			// All checks passed, transform!
			currentPlayer = player.entityPos
			CallFunction(PlayerObject_SuperTransform)
		else
			// Check if the player should do arial abilities

			// Can't do anything if invincible
			CheckEqual(player.invincible, 0)
			temp0 = checkResult

			// Can't do anything if Super, either
			CheckNotEqual(PlayerObject_SuperState, 1)
			temp0 &= checkResult

			if temp0 == true // All checks passed?
				// Determine ability with the player's current shield
				switch player.shield
				case SHIELD_NONE
					if options.shieldType > 1 // Are S3K shields on?

						PlaySfx(SfxName[Insta Shield], 0)

						// Set the shield object slot to hold the insta shield
						if object[+playerCount].type == TypeName[Blank Object]
							currentPlayer = player.entityPos
							arrayPos0 = currentPlayer
							arrayPos0 += playerCount
							ResetObjectEntity(arrayPos0, TypeName[Insta Shield], 0, 0, 0)
							object[arrayPos0].priority = PRIORITY_ACTIVE
						end if


						player.doubleJumpState = 2

						// Set insta shield state to active
						object[+playerCount].state = 1
						
						// Remove jump lock
						if player.state == PlayerObject_RollingJump
							player.state = PlayerObject_HandleAir
						end if

					end if
					break

				case SHIELD_NORMAL
					// Normal blue shield - no reaction
					break

				case SHIELD_BUBBLE
					// Start the bubble bounce
					PlaySfx(SfxName[Bubble Bounce], 0)

					player.yvel = 0x80000
					player.xvel = 0
					player.speed = player.xvel
					player.doubleJumpState = 2
					player.state = PlayerObject_BubbleBounce

					// Set the bubble shield state to falling
					object[+playerCount].state = 2

					break

				case SHIELD_FIRE
					PlaySfx(SfxName[Fire Dash], 0)

					// Determine launch direction based on where the player's currently facing
					GetBit(temp0, player.direction, 0)

					if temp0 == false
						player.xvel = 0x80000
					else
						player.xvel = -0x80000
					end if

					player.speed = player.xvel
					player.yvel = 0
					player.doubleJumpState = 2

					// Lock the camera momentarily
					camera.lockTimer = 15
					screen.cameraStyle = 4

					// Remove jump lock
					if player.state == PlayerObject_RollingJump
						player.state = PlayerObject_HandleAir
					end if

					// Set the flame shield to active and make it match the player's direction
					object[+playerCount].state = 2
					object[+playerCount].direction = player.direction
					break

				case SHIELD_LIGHTNING
					PlaySfx(SfxName[Lightning Jump], 0)

					// Double jump
					player.yvel = -0x58000
					player.doubleJumpState = 2

					// Create the spark objects
					CreateTempObject(TypeName[Lightning Spark], 0, player.xpos, player.ypos)
					object[tempObjectPos].xvel = -0x20000
					object[tempObjectPos].yvel = -0x20000

					CreateTempObject(TypeName[Lightning Spark], 0, player.xpos, player.ypos)
					object[tempObjectPos].xvel = 0x20000
					object[tempObjectPos].yvel = -0x20000

					CreateTempObject(TypeName[Lightning Spark], 0, player.xpos, player.ypos)
					object[tempObjectPos].xvel = -0x20000
					object[tempObjectPos].yvel = 0x20000

					CreateTempObject(TypeName[Lightning Spark], 0, player.xpos, player.ypos)
					object[tempObjectPos].xvel = 0x20000
					object[tempObjectPos].yvel = 0x20000

					// Remove jump lock
					if player.state == PlayerObject_RollingJump
						player.state = PlayerObject_HandleAir
					end if
					break

				end switch
			end if
		end if
	end if

end function

function PlayerObject_HyperDoubleJump
	if object.jumpPress == 1
					object.value44 = 1
					temp0 = 0x160000
					if object.direction != 1
						object.speed = 0xB0000
					else
						object.speed = -0xB0000
					end if
					object.yvel = -0x224A0
					object.value35 = 2
					CreateTempObject(TypeName[Lightning Spark], 0, object.xpos, object.ypos)
					object[tempObjectPos].xvel = -0x20000
					object[tempObjectPos].yvel = -0x20000
					CreateTempObject(TypeName[Lightning Spark], 0, object.xpos, object.ypos)
					object[tempObjectPos].xvel = 0x20000
					object[tempObjectPos].yvel = -0x20000
					CreateTempObject(TypeName[Lightning Spark], 0, object.xpos, object.ypos)
					object[tempObjectPos].xvel = -0x20000
					object[tempObjectPos].yvel = 0x20000
					CreateTempObject(TypeName[Lightning Spark], 0, object.xpos, object.ypos)
					object[tempObjectPos].xvel = 0x20000
					object[tempObjectPos].yvel = 0x20000
					if object.state == PlayerObject_RollingJump
						object.state = PlayerObject_HandleAir
					end if
	end if
end function

function PlayerObject_StartPeelout
	player.state = PlayerObject_HandlePeelout
	player.animation = ANI_PEELOUT
	player.abilityTimer = 0
	PlaySfx(SfxName[Peelout Charge], 0)
end function

function PlayerObject_HandlePeelout
	if player.gravity == GRAVITY_AIR
		player.state = PlayerObject_HandleAir
		player.speed = 0
		end if
	if Player.prevGravity==4096
		if Player.AbilityTimer<393216
			Player.AbilityTimer+=24576
		end if
	else
		if Player.AbilityTimer<786432
			Player.AbilityTimer+=24576
		end if
	endif
	if Player.AbilityTimer<390594
		Player.Animation=ANI_WALKING
		temp0=Player.AbilityTimer
		temp0>>=16
		temp0*=80
		temp0/=6
		temp0+=20
	else
		temp0=Player.AbilityTimer
		temp0>>=16
		temp0*=80
		temp0/=6
		if Player.AbilityTimer>655359
			Player.Animation=ANI_PEELOUT
		else
			Player.Animation=ANI_RUNNING
		end if
	endif
	if Player.Up==0
		Player.State=PlayerObject_HandleGround
		if Player.AbilityTimer<390594
			Player.Speed=0
		else
			Player.Speed=Player.AbilityTimer
			if Player.Direction==FACING_LEFT
				FlipSign(Player.Speed)
			endif
			PlaySfx(SfxName[Peelout Release], 0)
		end if
		CallFunction(PlayerObject_ResetOnFloor)
	end if
end function

// Tails's ability
function PlayerObject_StartTailsFlight

	// Only act if the jump button is pressed
	if player.jumpPress == true
		// Following checks are to see if Tails should transform instead of doing arial abilities

		// All 7 emeralds collected?
		CheckEqual(specialStage.emeralds, 0b01111111)
		temp0 = checkResult

		// Tails needs to have at least 50 rings
		CheckGreater(player.rings, 49)
		temp0 &= checkResult

		// He can't already be Super, of course
		CheckNotEqual(PlayerObject_SuperState, 1)
		temp0 &= checkResult

		// And don't let Tails transform if the level is over, either
		CheckNotEqual(object[30].type, TypeName[Act Finish])
		temp0 &= checkResult

		if temp0 == true
			// All checks passed, go Super!
			currentPlayer = player.entityPos
			CallFunction(PlayerObject_SuperTransform)
		else
			// Make Tails start flying
			player.timer = 0
			player.state = PlayerObject_HandleTailsFlight
			player.flightVelocity = 0x800
			if player.gravityValue == 0x3800
				PlaySfx(SfxName[Flying], 1)
				player.animation = ANI_FLYING
			else
				player.animation = ANI_SWIMMING
			end if
		end if
	end if

end function


// Knuckles's ability
function PlayerObject_StartKnuxGlide

	// Only act if the jump button is pressed
	if player.jumpPress == true

		// Following checks are to see if Sonic transform instead of doing arial abilities

		// Note that this isn't 127 emeralds, but rather 01111111 in binary
		CheckEqual(specialStage.emeralds, 0b01111111)
		temp0 = checkResult

		// Player needs to have at least 50
		CheckGreater(player.rings, 49)
		temp0 &= checkResult

		// Player can't already be Super, of course
		CheckNotEqual(PlayerObject_SuperState, 1)
		temp0 &= checkResult

		// Don't let the player transform if the level is over, either
		CheckNotEqual(object[30].type, TypeName[Act Finish])
		temp0 &= checkResult

		if temp0 == true
			// All checks passed, go Super!
			currentPlayer = player.entityPos
			CallFunction(PlayerObject_SuperTransform)
		else

			// Start gliding
			player.speed = 0x40000
			if player.yvel < 0
				player.yvel = 0
			end if

			if player.direction == FACING_RIGHT
				player.state = PlayerObject_KnuxGlideRight
				player.xvel = 0x40000
				player.timer = 0
			else
				player.state = PlayerObject_KnuxGlideLeft
				player.xvel = -0x40000
				player.timer = 256
			end if

			player.animation = ANI_GLIDING
			player.frame = 2

		end if

	end if

end function


function PlayerObject_HandleGround

	if player.animation != ANI_SKIDDING
		temp7 = true
	else
		temp7 = false
	end if

	CallFunction(PlayerObject_HandleMovement)

	if player.gravity == GRAVITY_AIR

		player.state = PlayerObject_HandleAir
		CallFunction(PlayerObject_HandleAirMovement)

	else

		CallFunction(PlayerObject_ResetOnFloor)

		if player.speed == 0

			if player.collisionMode == CMODE_FLOOR

				if player.timer < 240
					player.animation = ANI_STOPPED
					player.timer++
				else

					player.animation = ANI_WAITING

					if stage.playerListPos == PlayerName[KNUCKLES]

						player.timer++

						if player.timer == 834
							player.timer = 0
							player.animation = ANI_STOPPED
						end if

					end if

				end if

				// Update balancing animation
				switch player.character
				case PLAYER_SONIC_ALONE
				case PLAYER_TAILS_ALONE
					if player.floorSensorC == false
						if player.floorSensorR == false
							player.timer = 0
							player.animation = ANI_FLAILING1
							player.direction = FACING_RIGHT
						end if
						if player.floorSensorL == false
							player.timer = 0
							player.animation = ANI_FLAILING1
							player.direction = FACING_LEFT
						end if
					end if
					break

				case PLAYER_KNUX_ALONE
					if player.floorSensorC == false
						if player.floorSensorR == false
							player.timer = 0
							player.animation = ANI_FLAILING1
							if player.direction == FACING_LEFT
								player.prevAnimation = ANI_FLAILING1
								player.frame = 4
								player.animationTimer = 0
								player.animationSpeed = 0
							end if
							player.direction = FACING_RIGHT
						end if
						if player.floorSensorL == false
							player.timer = 0
							player.animation = ANI_FLAILING1
							if player.direction == FACING_RIGHT
								player.prevAnimation = ANI_FLAILING1
								player.frame = 4
								player.animationTimer = 0
								player.animationSpeed = 0
							end if
							player.direction = FACING_LEFT
						end if
					end if
					break

				end switch

			end if

		else

			player.timer = 0

			if player.speed > 0
				if player.speed < 0x5F5C2
					player.animation = ANI_WALKING
					CallFunction(PlayerObject_HandleWalkAnimSpd)
				else

					if player.speed > 0x9FFFF
						player.animation = ANI_PEELOUT
					else
						player.animation = ANI_RUNNING
					end if

					CallFunction(PlayerObject_HandleRunAnimSpd)

				end if
			else

				if player.speed > -0x5F5C2
					player.animation = ANI_WALKING
					CallFunction(PlayerObject_HandleWalkAnimSpd)
				else

					if player.speed < -0x9FFFF
						player.animation = ANI_PEELOUT
					else
						player.animation = ANI_RUNNING
					end if

					CallFunction(PlayerObject_HandleRunAnimSpd)

				end if

			end if

		end if

		if player.skidding > 0

			if temp7 == true
				PlaySfx(SfxName[Skidding], 0)
			end if

			player.animation = ANI_SKIDDING
			player.animationSpeed = 0
			player.skidding--

			if ringTimer == 0

				// Create a dust puff object
				CreateTempObject(TypeName[Dust Puff], 0, player.xpos, player.ypos)
				object[tempObjectPos].iypos += player.collisionBottom
				object[tempObjectPos].drawOrder = player.sortedDrawOrder

			end if

			if player.speed > 0
				player.direction = FACING_RIGHT
			else
				player.direction = FACING_LEFT
			end if

		end if

		if player.collisionMode == CMODE_FLOOR
			if player.pushing == 2
				player.animation = ANI_PUSHING
				player.animationSpeed = 0
			end if
		end if

		if player.jumpPress == true
			CallFunction(PlayerObject_StartJump)
		else

			if player.up == true
				if player.speed == 0

					if player.animation != ANI_FLAILING1
						if player.animation != ANI_FLAILING2
							player.state = PlayerObject_LookingUp
							player.timer = 0
						else
							player.up = false
							player.down = false
						end if
					else
						player.up = false
						player.down = false
					end if

				end if
			end if

			if player.down == true
				if player.speed == 0
					if player.animation != ANI_FLAILING1
						if player.animation != ANI_FLAILING2
							player.state = PlayerObject_Crouching
							player.timer = 0
						else
							player.up = false
							player.down = false
						end if
					else
						player.up = false
						player.down = false
					end if
				else
					if player.left == false
						if player.right == false

							if player.speed > 0
								if player.speed > 0x8800

									// Start rolling
									player.state = PlayerObject_HandleRoll
									player.animation = ANI_JUMPING
									if player.prevAnimation != ANI_JUMPING
										player.iypos -= player.jumpOffset
									end if
									player.abilityTimer = 0x400
									PlaySfx(SfxName[Rolling], 0)

								end if
							else
								if player.speed < -0x8800

									// Start rolling
									player.state = PlayerObject_HandleRoll
									player.animation = ANI_JUMPING
									if player.prevAnimation != ANI_JUMPING
										player.iypos -= player.jumpOffset
									end if
									player.abilityTimer = 0x400
									PlaySfx(SfxName[Rolling], 0)

								end if
							end if

						end if
					end if
				end if
			end if
		end if
	end if

end function


function PlayerObject_HandleAir

	CallFunction(PlayerObject_HandleAirAcceleration)

	// Only do this if actually in the air
	if player.gravity == GRAVITY_AIR

		CallFunction(PlayerObject_HandleAirMovement)

		if player.yvel > 0x20000
			if player.animation == ANI_FLAILING1
				player.animation = ANI_WALKING
			end if
			if player.animation == ANI_FLAILING2
				player.animation = ANI_WALKING
			end if
		end if

		// After hitting a spring
		if player.animation == ANI_BOUNCING
			if player.yvel >= 0
				if player.animationReserve == ANI_STOPPED
					player.animationReserve = ANI_WALKING
				end if
				player.animation = player.animationReserve
			end if
		end if

		if player.animation == ANI_HURT
			if player.yvel >= 0
				if player.animationReserve == ANI_STOPPED
					player.animationReserve = ANI_WALKING
				end if
				player.animation = player.animationReserve
			end if
		end if

		// Allow the player to use their jump ability if the conditions are met
		// (In order...)
		// - Must be rolling
		// - Must have the jump ability flag set
		// - Must be after a jump
		if player.animation == ANI_JUMPING
			if player.doubleJumpState == 1
				if player.yvel >= player.jumpStrength
					CallFunction(player.jumpAbility)
				end if
			end if
		end if

		if player.animation == ANI_SKIDDING
			if player.skidding > 0
				player.skidding--
			else
				player.animation = ANI_WALKING
				player.prevAnimation = ANI_WALKING
				player.frame = 0
				player.animationSpeed = 40
			end if
		end if

	else

		player.state = PlayerObject_HandleGround
		CallFunction(PlayerObject_ResetOnFloor)
		player.skidding = 0

	end if

end function


// Note that the actual control lock is set in PlayerObject_RollingJump, not here
function PlayerObject_AirCtrlLock

	CallFunction(PlayerObject_HandleAirAcceleration)

	if player.gravity == GRAVITY_AIR
		CallFunction(PlayerObject_HandleAirMovement)
	else

		player.state = PlayerObject_Unknown2 // "PlayerObject_HandleRollDecel"
		CallFunction(PlayerObject_ResetOnFloor)

		player.skidding = 0

	end if

	player.animation = ANI_JUMPING

end function


function PlayerObject_HandleRoll

	CallFunction(PlayerObject_HandleRolling)

	if player.gravity == GRAVITY_AIR

		player.state = PlayerObject_HandleAir
		player.timer = 0
		CallFunction(PlayerObject_HandleAirMovement)

	else

		CallFunction(PlayerObject_HandleRollAnimSpd)

		player.animationSpeed = player.CustomRollAnimSpeed
		CallFunction(PlayerObject_ResetOnFloor)

		if player.jumpPress == true
			CallFunction(PlayerObject_StartJump)
		end if

	end if

end function


// Jumping while rolling leads you to here instead of the normal air function
function PlayerObject_RollingJump

	// Control lock
	player.left = false
	player.right = false

	CallFunction(PlayerObject_HandleAirAcceleration)

	if player.gravity == GRAVITY_AIR

		// Allow the player to use their jump ability, given the conditions are met
		// - Must be rolling
		// - Must have the jump ability flag set
		// - Must be after a jump
		if player.animation == ANI_JUMPING
			if player.doubleJumpState == 1
				if player.yvel >= player.jumpStrength
					CallFunction(player.jumpAbility)
				end if
			end if

		end if

		CallFunction(PlayerObject_HandleAirMovement)

	else

		player.state = PlayerObject_HandleGround
		CallFunction(PlayerObject_ResetOnFloor)
		player.skidding = 0

	end if

end function


// Also handles moving the camera up
function PlayerObject_LookingUp

	if player.up == false
		player.state = PlayerObject_HandleGround
		player.timer = 0
	else

		if player.timer < 60
			player.timer++
		else

			temp0 = player.ypos
			temp0 >>= 16
			temp0 -= screen.cameraY
			temp0 -= 112
			if player.lookPosY > temp0
				player.lookPosY -= 2
			end if

		end if

		player.animation = ANI_LOOKINGUP

		if player.gravity == GRAVITY_AIR
			player.state = PlayerObject_HandleAir
			player.timer = 0
		else
			if player.jumpPress == true
				CallFunction(PlayerObject_StartPeelout)
			end if
		end if

	end if

end function


// Also handles moving the camera down
function PlayerObject_Crouching

	if player.down == false
		player.state = PlayerObject_HandleGround
		player.timer = 0
	else

		if player.timer < 60
			player.timer++
		else
			temp0 = player.ypos
			temp0 >>= 16
			temp0 -= screen.cameraY
			temp0 += 96
			if player.lookPosY < temp0
				player.lookPosY += 2
			end if
		end if

		player.animation = ANI_LOOKINGDOWN

		if player.gravityValue == GRAVITY_AIR
			player.state = PlayerObject_HandleAir
			player.timer = 0
		else
			if player.jumpPress == true
				CallFunction(player[0].spindashFunction)
			end if
		end if

	end if

end function


// While the player is charging the spindash
function PlayerObject_HandleSpindash

	if player.gravity == GRAVITY_AIR
		player.state = PlayerObject_HandleAir
		player.speed = 0
	end if

	if player.jumpPress == true

		// Rev up the spindash
	
		if player.abilityTimer < 0x90000
			player.abilityTimer += 0x20000
		else
			player.abilityTimer = 0x80000
		end if

		player.frame = 0
		PlaySfx(SfxName[Charge], 0)

	else

		temp0 = player.abilityTimer
		temp0 >>= 5
		player.abilityTimer -= temp0

	end if


	if player.down == false

		// Release the spindash!

		player.timer = 0
		player.state = PlayerObject_HandleRoll
		player.animation = ANI_JUMPING
		player.iypos -= player.jumpOffset

		if player.entityPos == 0
			camera.lockTimer = 15
			screen.cameraStyle = 4
		end if

		temp0 = player.abilityTimer
		temp0 >>= 17
		temp0 <<= 16

		if PlayerObject_SuperState == 1
			temp0 += 0xB0000
		else
			temp0 += 0x80000
		end if

		if player.direction == FACING_RIGHT
			player.speed = temp0
		else
			player.speed = temp0
			FlipSign(player.speed)
		end if

		StopSfx(SfxName[Charge])
		PlaySfx(SfxName[Release], 0)

		CallFunction(PlayerObject_ResetOnFloor)

	end if

end function


// P2 Tails starts flying for Tails Assist
function PlayerObject_StartFlyCarry

	if player.tailsGrabTimeout != 0
		player.tailsGrabTimeout--
	end if

	temp0 = player.xpos
	temp1 = player.ypos
	temp1 += 0x1F0000

	if player[0].animation == ANI_JUMPING
		temp1 += 0x50000
	end if

	temp0 -= player[0].xpos
	temp1 -= player[0].ypos

	if player[0].state != PlayerObject_FlyCarry

		CheckEqual(player[0].gravity, GRAVITY_GROUND)
		temp2 = checkResult

		CheckGreater(player.yvel, 0)
		temp2 &= checkResult

		if temp2 == false
			CheckEqual(player[0].state, PlayerObject_HandleGround)
			temp2 = checkResult
			CheckEqual(player[0].state, PlayerObject_HandleRoll)
			temp2 |= checkResult
			CheckEqual(player[0].state, PlayerObject_HandleAir)
			temp2 |= checkResult
			CheckEqual(player[0].state, PlayerObject_RollingJump)
			temp2 |= checkResult
			CheckEqual(player[0].state, PlayerObject_LookingUp)
			temp2 |= checkResult
			CheckEqual(player[0].state, PlayerObject_Crouching)
			temp2 |= checkResult
			
			if temp2 != 0
				temp2 = temp0
				Abs(temp2)
				temp3 = temp1
				Abs(temp3)
				if temp2 <= 0x80000
					if temp3 <= 0x80000
						if player.tailsGrabTimeout == 0
							if player[0].down == false
								player[0].animation = ANI_HANGING
								player[0].state = PlayerObject_FlyCarry
								player[0].xpos += temp0
								player[0].ypos += temp1
								PlaySfx(SfxName[Catch], 0)
							end if
						end if
					end if
				end if
			end if
		end if
	end if

	if player[0].state == PlayerObject_FlyCarry

		temp2 = player.xpos
		temp3 = player.ypos
		temp6 = player.xvel
		temp7 = player.yvel

		ProcessObjectMovement()

		PlayerObject_SidekickX = player.xpos
		PlayerObject_SidekickY = player.ypos

		temp4 = player.xpos
		temp4 &= -0x10000

		temp5 = player.ypos
		temp5 &= -0x10000
		temp5 += 0x1F0000

		player.xpos = temp2
		player.ypos = temp3
		player.xvel = temp6
		player.yvel = temp7

		// "Move" to P1's entity slot
		stage.entityPos = 0

		temp0 = player.xpos
		temp0 &= -0x10000
		temp1 = player.ypos
		temp1 &= -0x10000

		player.xvel = temp4
		player.yvel = temp5
		player.xvel -= temp0
		player.yvel -= temp1

		ProcessObjectMovement()

		// "Move" back to P2's entity slot
		stage.entityPos = 1

		player[0].collisionPlane = player.collisionPlane
		player[0].speed = player.speed
		player[0].speed = player.direction

		PlayerObject_LeaderX = player[0].xpos
		PlayerObject_LeaderY = player[0].ypos

		temp2 = player[0].xpos
		temp2 &= -0x10000
		temp3 = player[0].ypos
		temp3 &= -0x10000

		CheckNotEqual(temp4, temp2)
		temp6 = checkResult
		CheckNotEqual(temp5, temp3)
		temp6 |= checkResult

		if temp6 == true

			if player[0].gravity == GRAVITY_GROUND
				player[0].state = PlayerObject_HandleGround
			else
				player[0].state = PlayerObject_HandleAir
			end if

			player.tailsGrabTimeout = 30 // 30 frame delay before Sonic can grab Tails again

		end if

	end if

end function


// Main character gets picked up by P2 Tails
function PlayerObject_FlyCarry

	if player[1].state != PlayerObject_HandleTailsFlight
		player.state = PlayerObject_HandleAir
	end if

	temp0 = player[1].xpos
	temp0 &= -0x10000

	temp2 = player.xpos
	temp2 &= -0x10000

	if player.xpos == PlayerObject_LeaderX
		PlayerObject_SidekickX &= -0x10000
		temp1 = temp0
		temp1 -= PlayerObject_SidekickX
		temp2 += temp1
	end if

	if temp0 != temp2
		if player.gravity == GRAVITY_GROUND
			player.state = PlayerObject_HandleGround
		else
			player.state = PlayerObject_HandleAir
		end if
	end if

	if player.gravity == GRAVITY_GROUND
		if player.yvel >= 0
			player.state = PlayerObject_HandleGround
		end if
	end if

	if player.jumpPress != 0
		if player.down != 0
			if player.gravityValue == 0x3800
				player.yvel = -0x40000
			else
				player.yvel = -0x20000
			end if
			player.state = PlayerObject_HandleAir
			player.animation = ANI_JUMPING
		end if
	end if

	if player.state == PlayerObject_FlyCarry
		player.xvel = 0
		player.yvel = 0
		player.speed = 0
	else
		player[1].tailsGrabTimeout = 30 // 30 frame delay before Sonic can grab Tails again
	end if
end function


// Used by both normal Tails and P2/AI Tails
function PlayerObject_HandleTailsFlight

	CallFunction(PlayerObject_HandleAirAcceleration)

	if player.gravity == GRAVITY_AIR
		player.xvel = player.speed
		if player.yvel < -0x10000
			player.flightVelocity = 0x800
		else
			if player.yvel < 1
				if player.abilityTimer < 60
					player.abilityTimer++
				else
					player.flightVelocity = 0x800
				end if
			end if
		end if

		player.yvel += player.flightVelocity

		if player.ypos < 0x100000
			if player.yvel < 0
				player.yvel = 0
			end if
		end if

		CallFunction(PlayerObject_StartFlyCarry)

		if player.timer < 480
			if player.gravityValue == 0x3800
				if player[0].state == PlayerObject_FlyCarry
					if player.yvel < 0
						player.animation = ANI_FLY_LIFT_UP
						player.animationSpeed = 240
					else
						player.animation = ANI_FLY_LIFT_DOWN
						player.animationSpeed = 120
					end if
				else
					player.animation = ANI_FLYING
					if player.yvel < 0
						player.animationSpeed = 240
					else
						player.animationSpeed = 120
					end if
				end if
			else
				if player[0].state == PlayerObject_FlyCarry
					player.animation = ANI_SWIM_LIFT
				else
					player.animation = ANI_SWIMMING
					if player.yvel < 0
						player.animationSpeed = 60
					else
						player.animationSpeed = 30
					end if
				end if
			end if

			player.timer++

			if player.timer == 480
				if player.gravityValue == 0x3800
					if player[0].state == PlayerObject_FlyCarry
						player.animation = ANI_FLY_LIFT_TIRED
					else
						player.animation = ANI_FLYINGTIRED
					end if
					player.animationSpeed = 120
					StopSfx(SfxName[Flying])
					PlaySfx(SfxName[Tired], 1)
				else
					if player[0].state == PlayerObject_FlyCarry
						player.animation = ANI_SWIM_LIFT
					else
						player.animation = ANI_SWIMMINGTIRED
					end if
				end if
			else
				if player.jumpPress == true
					CheckNotEqual(player.gravityValue, 0x3800)
					temp0 = checkResult
					CheckEqual(player[0].state, PlayerObject_FlyCarry)
					temp0 &= checkResult
					if temp0 == false
						player.flightVelocity = -0x2000
						player.abilityTimer = 0
					end if
				end if
			end if
		else
			if player.gravityValue == 0x3800
				if player[0].state == PlayerObject_FlyCarry
					player.animation = ANI_FLY_LIFT_TIRED
				else
					player.animation = ANI_FLYINGTIRED
				end if
			else
				if player[0].state == PlayerObject_FlyCarry
					player.animation = ANI_SWIM_LIFT
				else
					player.animation = ANI_SWIMMINGTIRED
				end if
			end if
		end if
	else
		player.animation = ANI_WALKING
		player.state = PlayerObject_HandleGround
		CallFunction(PlayerObject_ResetOnFloor)
	end if
end function


function PlayerObject_KnuxGlideLeft

	if player.gravity == GRAVITY_AIR
		if player.jumpHold == true
			if player.timer == 256
				if player.speed < 0x180000
					player.speed += 0x400
				end if
			else
				if player.speed < 0x40000
					player.speed += 0x1000
				end if
			end if
			if player.yvel > 0x8000
				player.yvel -= 0x2000
			else
				player.yvel += 0x2000
			end if
			if player.timer < 256
				player.timer += 4
			end if
			if player.timer < 170
				if player.timer > 86
					player.frame = 0
				else
					if player.timer > 44
						player.frame = 1
					else
						player.frame = 2
					end if
				end if
			else
				if player.timer < 212
					player.frame = 1
				else
					player.frame = 2
				end if
			end if
			temp7 = player.xpos
			if player.timer < 128
				player.direction = FACING_RIGHT
				temp0 = 0
				temp1 = 0
			else
				player.direction = FACING_LEFT
				player.xpos = temp7
				player.xpos += player.xvel
				player.ypos = player.ypos
				ObjectTileCollision(CSIDE_RWALL, -12, -2, player.collisionPlane)
				temp0 = checkResult
				temp2 = player.xpos
				player.xpos = temp7
				player.xpos += player.xvel
				ObjectTileCollision(CSIDE_RWALL, -12, 11, player.collisionPlane)
				temp1 = checkResult
				temp3 = player.xpos
			end if
			Cos(player.xvel, player.timer)
			player.xvel *= player.speed
			player.xvel >>= 9
			if player.right == true
				player.state = PlayerObject_KnuxGlideRight
			end if
			player.xpos = temp7
			checkResult = temp0
			checkResult &= temp1
			if checkResult == true
				if temp2 == temp3
					player.state = PlayerObject_KnuxWallClimb
					player.speed = 0
					player.xvel = 0
					player.yvel = 0
					player.timer = 0
					PlaySfx(SfxName[Catch], 0)
				else
					player.timer = 0
					player.xvel >>= 2
					player.speed = player.xvel
					player.animation = ANI_GLIDING_DROP
					player.state = PlayerObject_KnuxGlideDrop
				end if
			else
				if temp0 == true
					player.timer = 0
					player.xvel >>= 2
					player.speed = player.xvel
					player.animation = ANI_GLIDING_DROP
					player.state = PlayerObject_KnuxGlideDrop
				end if
			end if
		else
			player.timer = 0
			player.xvel >>= 2
			player.speed = player.xvel
			player.animation = ANI_GLIDING_DROP
			player.state = PlayerObject_KnuxGlideDrop
		end if
	else
		if player.collisionMode == CMODE_FLOOR
			player.timer = 0
			player.state = PlayerObject_KnuxGlideSlide
			player.animation = ANI_GLIDING_STOP
			player.speed = player.xvel
		else
			player.state = PlayerObject_HandleGround
			CallFunction(PlayerObject_ResetOnFloor)
			player.skidding = 0
		end if
	end if

	temp0 = stage.curYBoundary1
	temp0 += 16
	temp0 <<= 16

	if player.ypos < temp0
		player.xvel = 0
		player.speed = player.xvel
	end if

end function


function PlayerObject_KnuxGlideRight
	if player.gravity == GRAVITY_AIR
		if player.jumpHold == true
			if player.timer == 0
				if player.speed < 0x180000
					player.speed += 0x400
				end if
			else
				if player.speed < 0x40000
					player.speed += 0x1000
				end if
			end if
			if player.yvel > 0x8000
				player.yvel -= 0x2000
			else
				player.yvel += 0x2000
			end if
			if player.timer > 0
				player.timer -= 4
			end if
			if player.timer < 170
				if player.timer > 86
					player.frame = 0
				else
					if player.timer > 44
						player.frame = 1
					else
						player.frame = 2
					end if
				end if
			else
				if player.timer < 212
					player.frame = 1
				else
					player.frame = 2
				end if
			end if
			temp7 = player.xpos
			if player.timer < 128
				player.direction = FACING_RIGHT
				player.xpos = temp7
				player.xpos += player.xvel
				player.ypos = player.ypos
				ObjectTileCollision(CSIDE_LWALL, 12, -2, player.collisionPlane)
				temp0 = checkResult
				temp2 = player.xpos
				player.xpos = temp7
				player.xpos += player.xvel
				ObjectTileCollision(CSIDE_LWALL, 12, 11, player.collisionPlane)
				temp1 = checkResult
				temp3 = player.xpos
			else
				player.direction = FACING_LEFT
				temp0 = 0
				temp1 = 0
			end if
			Cos(player.xvel, player.timer)
			player.xvel *= player.speed
			player.xvel >>= 9
			if player.left == true
				player.state = PlayerObject_KnuxGlideLeft
			end if
			player.xpos = temp7
			checkResult = temp0
			checkResult &= temp1
			if checkResult == true
				temp2 >>= 1
				temp3 >>= 1
				if temp2 == temp3
					player.state = PlayerObject_KnuxWallClimb
					player.speed = 0
					player.xvel = 0
					player.yvel = 0
					player.timer = 0
					PlaySfx(SfxName[Catch], 0)
				else
					player.timer = 0
					player.xvel >>= 2
					player.speed = player.xvel
					player.animation = ANI_GLIDING_DROP
					player.state = PlayerObject_KnuxGlideDrop
				end if
			else
				if temp0 == true
					player.speed = 0
					player.timer = 0
					player.xvel >>= 2
					player.speed = player.xvel
					player.animation = ANI_GLIDING_DROP
					player.state = PlayerObject_KnuxGlideDrop
				end if
			end if
		else
			player.timer = 0
			player.xvel >>= 2
			player.speed = player.xvel
			player.animation = ANI_GLIDING_DROP
			player.state = PlayerObject_KnuxGlideDrop
		end if
	else
		if player.collisionMode == CMODE_FLOOR
			player.timer = 0
			player.state = PlayerObject_KnuxGlideSlide
			player.animation = ANI_GLIDING_STOP
			player.speed = player.xvel
		else
			player.state = PlayerObject_HandleGround
			CallFunction(PlayerObject_ResetOnFloor)
			player.skidding = 0
		end if
	end if
	temp0 = stage.curYBoundary1
	temp0 += 16
	temp0 <<= 16
	if player.ypos < temp0
		player.xvel = 0
		player.speed = player.xvel
	end if
end function


function PlayerObject_KnuxGlideDrop
	if player.gravity == GRAVITY_AIR
		CallFunction(PlayerObject_HandleAirAcceleration)
		CallFunction(PlayerObject_HandleAirMovement)
	else
		if player.timer == 0
			PlaySfx(SfxName[Landing], 0)
		end if
		player.scrollTracking = 0
		player.speed = 0
		player.xvel = 0
		player.animation = ANI_LOOKINGDOWN
		player.prevAnimation = ANI_LOOKINGDOWN
		player.frame = 2
		if player.timer < 16
			player.timer++
		else
			player.state = PlayerObject_HandleGround
			CallFunction(PlayerObject_ResetOnFloor)
			player.skidding = 0
		end if
	end if
end function


function PlayerObject_KnuxGlideSlide
	if player.gravityValue == GRAVITY_GROUND
		if player.speed == 0
			player.scrollTracking = 0
			player.frame = 1
			if player.timer < 16
				player.timer++
			else
				player.state = PlayerObject_HandleGround
				CallFunction(PlayerObject_ResetOnFloor)
				player.skidding = 0
			end if
		else
			if ringTimer == 0
				CreateTempObject(TypeName[Dust Puff], 0, player.xpos, player.ypos)
				object[tempObjectPos].iypos += player.collisionBottom
				object[tempObjectPos].drawOrder = player.sortedDrawOrder
				if player.timer == 0
					PlaySfx(SfxName[Sliding], 0)
					player.timer = 1
				else
					player.timer = 0
				end if
			end if
			player.frame = 0
			if player.speed > 0
				player.speed -= 0x2000
				if player.speed < 0
					player.speed = 0
					player.timer = 0
				end if
			else
				player.speed += 0x2000
				if player.speed > 0
					player.speed = 0
					player.timer = 0
				end if
			end if
			if player.jumpHold == false
				player.speed = 0
				player.timer = 0
			end if
		end if
		player.xvel = player.speed
	else
		player.timer = 0
		player.animation = ANI_GLIDING_DROP
		player.state = PlayerObject_KnuxGlideDrop
	end if
end function


function PlayerObject_KnuxWallClimb
	if player.gravity == GRAVITY_AIR
		player.animation = ANI_CLIMBING
		if player.up == true
			if PlayerObject_SuperState == 1
				player.yvel = -0x20000
			else
				player.yvel = -0x10000
			end if
			temp0 = player.collisionTop
			temp0 *= -0x10000
			if player.ypos < temp0
				player.ypos = temp0
			end if
			player.timer++
			if player.timer == 4
				player.timer = 0
				player.frame++
				player.frame %= 6
			end if
		else
			if player.down == true
				if PlayerObject_SuperState == 1
					player.yvel = 0x20000
				else
					player.yvel = 0x10000
				end if
				player.timer++
				if player.timer == 4
					player.timer = 0
					if player.frame < 1
						player.frame += 6
					end if
					player.frame--
				end if
			else
				player.yvel = 0
			end if
		end if
		if player.jumpPress == true
			player.animation = ANI_JUMPING
			player.state = PlayerObject_HandleAir
			player.timer = 0
			PlaySfx(SfxName[Jump], 0)
			if player.direction == FACING_LEFT
				player.xvel = 0x40000
				player.speed = 0x40000
				player.direction = FACING_RIGHT
			else
				player.xvel = -0x40000
				player.speed = -0x40000
				player.direction = FACING_LEFT
			end if
			player.yvel = -0x40000
			if player.gravityValue != 0x3800
				player.xvel >>= 1
				player.speed >>= 1
				player.yvel >>= 1
			end if
		else
			if player.direction == FACING_RIGHT
				temp2 = player.xpos
				ObjectTileGrip(CSIDE_LWALL, 10, -10, player.collisionPlane)
				temp0 = checkResult
				temp3 = player.xpos
				player.xpos = temp2
				ObjectTileGrip(CSIDE_LWALL, 10, 11, player.collisionPlane)
				temp1 = checkResult
				if player.xpos > temp3
					player.xpos = temp3
				end if
			else
				temp2 = player.xpos
				ObjectTileGrip(CSIDE_RWALL, -10, -10, player.collisionPlane)
				temp0 = checkResult
				temp3 = player.xpos
				player.xpos = temp2
				ObjectTileGrip(CSIDE_RWALL, -10, 11, player.collisionPlane)
				temp1 = checkResult
				if player.xpos < temp3
					player.xpos = temp3
				end if
			end if
			if temp0 == false
				player.xpos = temp2
				player.animation = ANI_LEDGEPULLUP
				player.yvel = 0
				player.timer = 0
				player.state = PlayerObject_KnuxLedgePullUp
				player.tileCollisions = false
				if player.direction == FACING_RIGHT
					player.xpos += 0x10000
				end if
			else
				if temp1 == false
					player.animation = ANI_GLIDING_DROP
					player.prevAnimation = ANI_GLIDING_DROP
					player.frame = 2
					player.timer = 0
					player.state = PlayerObject_KnuxGlideDrop
				end if
			end if
		end if
	else
		player.animation = ANI_WALKING
		player.state = PlayerObject_HandleGround
		CallFunction(PlayerObject_ResetOnFloor)
	end if
end function


function PlayerObject_KnuxLedgePullUp
	switch player.frame
	case 0
		if player.timer < 5
			ObjectTileGrip(CSIDE_FLOOR, 12, -9, player.collisionPlane)
			player.timer++
		else
			player.timer = 0
			player.frame++
			if player.direction == FACING_RIGHT
				player.xpos += 0x90000
			else
				player.xpos -= 0x90000
			end if
			player.ypos -= 0xA0000
		end if
		break
	case 1
		if player.timer < 5
			player.timer++
		else
			player.timer = 0
			player.frame++
			if player.direction == FACING_RIGHT
				player.xpos += 0x50000
			else
				player.xpos -= 0x50000
			end if
			player.ypos -= 0x20000
		end if
		break
	case 2
		if player.timer < 5
			player.timer++
		else
			player.timer = 0
			player.animation = ANI_STOPPED
			player.state = PlayerObject_HandleAir
			player.ypos -= 0xA0000
			player.tileCollisions = true
		end if
		break
	end switch
end function


// Ouch!
function PlayerObject_Hurt
	if player.isPlayer2 == false
		arrayPos0 = player.entityPos
		arrayPos0 += playerCount
		if player.shield != SHIELD_NONE
			temp0 = 1
			ResetObjectEntity(arrayPos0, TypeName[Blank Object], 0, 0, 0)
			player.shield = SHIELD_NONE
			CheckEqual(options.spikeBehavior, 0)
			temp1 = checkResult
			CheckNotEqual(player.invulnerable, 0)
			temp1 &= checkResult
			if temp1 == true
				PlaySfx(SfxName[Spike], 0)
			else
				PlaySfx(SfxName[Hurt], 0)
			end if
		else
			if player[0].rings == 0
				if player.invulnerable != 0
					PlaySfx(SfxName[Spike], 0)
				else
					PlaySfx(SfxName[Hurt], 0)
				end if
				temp0 = 3
			else
				PlaySfx(SfxName[Lose Rings], 0)
				temp0 = 2
			end if
		end if
	else
		temp0 = 1
		if player.invulnerable != 0
			PlaySfx(SfxName[Spike], 0)
		else
			PlaySfx(SfxName[Hurt], 0)
		end if
	end if
	player.visible = true
	switch temp0
	case 1 // Shield recoil, also used by P2 - Ouch!
		player.state = PlayerObject_Knockback
		player.animation = ANI_HURT
		player.yvel = -0x40000
		player.gravity = GRAVITY_AIR
		player.scrollTracking = true
		player.tileCollisions = true
		player.invulnerable = 120
		if player.gravityValue == 0x1000
			player.speed >>= 1
			player.yvel >>= 1
		end if
		break
	case 2 // Lose Rings Recoil - Ouch!
		player.state = PlayerObject_Knockback
		player.animation = ANI_HURT
		player.yvel = -0x40000
		player.gravity = GRAVITY_AIR
		player.scrollTracking = true
		player.tileCollisions = true
		player.invulnerable = 120
		if player.gravityValue == 0x1000
			player.speed >>= 1
			player.yvel >>= 1
		end if
		temp0 = player[0].rings
		if temp0 > 16
			temp1 = temp0
			temp1 -= 16
			temp0 = 16
		else
			temp1 = 0
		end if
		if temp1 > 16
			temp1 = 16
		end if
		temp3 = temp1
		temp3 >>= 1
		temp3 <<= 5
		temp2 = 384
		temp2 -= temp3
		temp3 >>= 4
		if temp3 == temp1
			temp2 += 16
		else
			temp2 -= 16
		end if
		temp3 = 0
		while temp3 < temp1
			CreateTempObject(TypeName[Lose Ring], player.collisionPlane, player.xpos, player.ypos)
			Cos(object[tempObjectPos].xvel, temp2)
			Sin(object[tempObjectPos].yvel, temp2)
			object[tempObjectPos].xvel <<= 8
			object[tempObjectPos].yvel <<= 8
			object[tempObjectPos].animationSpeed = 256
			object[tempObjectPos].inkEffect = INK_ALPHA
			object[tempObjectPos].alpha = 256
			temp3++
			temp2 += 32
		loop
		temp3 = temp0
		temp3 >>= 1
		temp3 <<= 5
		temp2 = 384
		temp2 -= temp3
		temp3 >>= 4
		if temp3 == temp0
			temp2 += 16
		else
			temp2 -= 16
		end if
		temp3 = 0
		while temp3 < temp0
			CreateTempObject(TypeName[Lose Ring], player.collisionPlane, player.xpos, player.ypos)
			Cos(object[tempObjectPos].xvel, temp2)
			Sin(object[tempObjectPos].yvel, temp2)
			object[tempObjectPos].xvel <<= 9
			object[tempObjectPos].yvel <<= 9
			object[tempObjectPos].animationSpeed = 256
			object[tempObjectPos].inkEffect = INK_ALPHA
			object[tempObjectPos].alpha = 256
			temp3++
			temp2 += 32
		loop
		player[0].rings = 0
		ringExtraLife = 100
		break

	case 3 // Death - Gadzooks!
		player.sortedDrawOrder = 6
		player.speed = 0
		player.yvel = -0x70000
		player.xvel = 0
		player.state = PlayerObject_Death
		player.animation = ANI_DYING
		player.tileCollisions = false
		player.interaction = false
		if player.entityPos == 0
			player.priority = PRIORITY_ACTIVE_PAUSED
			if object[1].type == TypeName[Player 2 Object]
				object[1].priority = PRIORITY_ACTIVE_PAUSED
			end if
			screen.cameraEnabled = false
			stage.state = 3
		end if
		if object[+playerCount].type == invincibilityType
			object[+playerCount].propertyValue = 3
		end if
		object[+playerCount].type = TypeName[Blank Object]
		break

	end switch
end function


// Only knockback, no rings knocked out, no shields lost. Just a little scratch.
function PlayerObject_Knockback
	if player.gravity == GRAVITY_AIR
		player.scrollTracking = true
		if player.gravityValue == 0x3800
			player.yvel += 0x3000
		else
			player.yvel += 0xF00
		end if
		player.xvel = player.speed
	else
		player.state = PlayerObject_HandleGround
		player.speed = 0
		player.xvel = 0
		CallFunction(PlayerObject_ResetOnFloor)
	end if
end function


// Called by the player object itself to die - Gadzooks!
function PlayerObject_Death

	if player.entityPos == 0 // Player 1?
		if PlayerObject_SuperState == 1
			PlayerObject_SuperState = 2
		end if
	end if

	// Lock player control
	if player.controlMode != -1
		player.yvel = -0x70000
		player.controlMode = -1
	end if

	// Remove player invulnerability and make the player visible
	if player.invulnerable != 0
		player.invulnerable = 0
		player.visible = true
	end if

	player.yvel += 0x3800
	if player.animation != ANI_BORED
		player.animation = ANI_DYING
	end if
	if player.yvel > 0x100000
		if player.isPlayer2 == false
			player.lives--
			stage.timeEnabled = false
			player.type = TypeName[Death Event]
			deathEvent.drawOrder = 6
			deathEvent.leftTextPos = screen.xcenter
			deathEvent.leftTextPos -= 264
			deathEvent.rightTextPos = screen.xcenter
			deathEvent.rightTextPos += 200
			if options.gameMode == 2
				deathEvent.timer = 0
				deathEvent.state = 3
			else
				if player.lives == 0
					deathEvent.timer = -0xB40
					deathEvent.state = 0
					PlayMusic(5)
					stage.pauseEnabled = false
				else
					deathEvent.timer = 0
					deathEvent.state = 2
					if stage.minutes == 9
						if stage.seconds == 59
							if stage.milliSeconds == 99
								deathEvent.timer = -0xB40
								deathEvent.state = 1
								PlayMusic(5)
								stage.pauseEnabled = false
							end if
						end if
					end if
				end if
			end if
		end if
	end if
end function


// Called by the player object itself, not other objects
function PlayerObject_Drown
	if player.entityPos == 0 // Player 1?
		if PlayerObject_SuperState == 1
			PlayerObject_SuperState = 2
		end if
	end if
	player.controlMode = -1
	player.yvel += player.gravityValue
	player.animation = ANI_DROWNING
	if player.yvel > 0x80000
		if player.isPlayer2 == false
			if player.lives > 0
				player.lives--
			end if
			stage.timeEnabled = false
			player.type = TypeName[Death Event]
			deathEvent.drawOrder = 6
			deathEvent.leftTextPos = screen.xcenter
			deathEvent.leftTextPos -= 264
			deathEvent.rightTextPos = screen.xcenter
			deathEvent.rightTextPos += 200
			if options.gameMode == 2
				deathEvent.timer = 0
				deathEvent.state = 3
			else
				if player.lives == 0
					deathEvent.timer = -0xB40
					deathEvent.state = 0
					PlayMusic(5)
					stage.pauseEnabled = false
				else
					deathEvent.timer = 0
					deathEvent.state = 2
				end if
			end if
		end if
	end if
end function


// Get info about the tile the player is currently on
function PlayerObject_CheckTile
	if player.left == true
		player.direction = FACING_LEFT
		player.speed = -0x20000
		player.animationSpeed = 30
	else
		if player.right == true
			player.direction = FACING_RIGHT
			player.speed = 0x20000
			player.animationSpeed = 30
		else
			player.speed = 0
			player.animationSpeed = 0
		end if
	end if
	temp1 = player.xpos
	temp1 >>= 16
	temp2 = player.ypos
	temp2 >>= 16
	temp2 += player.collisionTop
	Get16x16TileInfo(temp0, temp1, temp2, TILEINFO_ANGLEB)
	if temp0 != 3
		player.state = PlayerObject_HandleAir
		player.speed = 0
		player.animationSpeed = 0
		player.yvel = 0
	end if
	if player.jumpPress == true
		player.state = PlayerObject_HandleAir
		player.yvel = 0
		player.speed = 0
		player.animationSpeed = 0
		player.ypos += 0x40000
	end if
	player.xvel = player.speed
end function


// Unused leftover from previous RSDK games
// Actual name would be "PlayerObject_CorkscrewRun"
function PlayerObject_Unknown1
	player.angle = 0
	CallFunction(PlayerObject_HandleMovement)
	player.animation = 34 // Corkscrew animation, it uses Nexus values and as such, it just looks like a jumbled mess in-game
	if player.speed < 393216
		if player.speed > -393216
			player.animation = ANI_WALKING
			player.state = PlayerObject_HandleAir
			player.rotation = 0
			if player.speed < 0
				player.direction = FACING_LEFT
			end if
		end if
	end if
	if player.down == true
		if player.speed > 6554
			player.state = PlayerObject_RollUnknown // "PlayerObject_CorkscrewRoll"
			player.animation = ANI_JUMPING
		end if
		if player.speed < -6554
			player.state = PlayerObject_RollUnknown // "PlayerObject_CorkscrewRoll"
			player.animation = ANI_JUMPING
		end if
	end if
	if player.skidding > 0
		if player.skidding == 16
			PlaySfx(8, 0) // Would be Skidding SFX if using Nexus SFX list
		end if
		player.animation = ANI_SKIDDING
		player.skidding--
	end if
	if player.jumpPress == true
		CallFunction(PlayerObject_StartJump)
	else
		CallFunction(PlayerObject_ResetOnFloor)
	end if
end function


// Unused leftover from previous RSDK games
// Actual name would be "PlayerObject_CorkscrewRoll"
function PlayerObject_RollUnknown
	player.angle = 0
	CallFunction(PlayerObject_HandleRolling)
	if player.speed < 393216
		if player.speed > -393216
			player.state = PlayerObject_HandleAir
		end if
	end if
	if player.jumpPress == true
		CallFunction(PlayerObject_StartJump)
	else
		CallFunction(PlayerObject_ResetOnFloor)
	end if
end function

// Actual name would be "PlayerObject_HandleRollDecel"
function PlayerObject_Unknown2
	if player.gravity == GRAVITY_AIR
		player.state = PlayerObject_AirCtrlLock
		player.timer = 0
		CallFunction(PlayerObject_HandleAirMovement)
	else
		if player.speed > 0
			if player.collisionMode == CMODE_FLOOR
				if player.speed < 0x10000
					player.speed = 0x40000
				end if
			end if
		else
			if player.collisionMode == CMODE_FLOOR
				if player.speed > -0x10000
					player.speed = -0x40000
				end if
			end if
		end if
		if player.right == true
			if player.speed < 0
				player.speed += player.rollingDecelerationP
			end if
		end if
		if player.left == true
			if player.speed > 0
				player.speed -= player.rollingDecelerationP
			end if
		end if
		if player.speed > 0
			player.speed -= player.rollingDecelerationA
			Sin256(temp0, player.angle)
			if temp0 > 0
				Sin256(temp0, player.angle)
				temp0 *= 0x5000
			else
				Sin256(temp0, player.angle)
				temp0 *= 0x1E00
			end if
			temp0 >>= 8
			player.speed += temp0
		else
			player.speed += player.rollingDecelerationA
			Sin256(temp0, player.angle)
			if temp0 < 0
				Sin256(temp0, player.angle)
				temp0 *= 0x5000
			else
				Sin256(temp0, player.angle)
				temp0 *= 0x1E00
			end if
			temp0 >>= 8
			player.speed += temp0
		end if
		CallFunction(PlayerObject_HandleRollAnimSpd)
		player.animationSpeed = player.CustomRollAnimSpeed
		CallFunction(PlayerObject_ResetOnFloor)
	end if
end function


// Used when hanging onto the LZ poles against the current
function PlayerObject_Clinging
	player.gravity = GRAVITY_AIR
	if player.animation != ANI_CLINGING
		player.xvel = 0x80000
		player.speed = 0x80000
	else
		player.xvel = 0
		player.speed = 0
	end if
	if player.up == true
		player.ypos -= 0x10000
	else
		if player.down == true
			player.ypos += 0x10000
		end if
	end if
	player.yvel = 0
end function


// Used for LZ's water slides
function PlayerObject_WaterSlide

	if player.gravity == GRAVITY_AIR

		// If the player is in the air, make them move to the HandleAir state
		player.state = PlayerObject_HandleAir
		player.angle = 0
		player.collisionMode = CMODE_FLOOR
		player.timer = 0
		CallFunction(PlayerObject_HandleAirMovement)
		player.animation = ANI_WATERSLIDE

	else

		if player.direction == FACING_RIGHT
			player.speed = 0xA0000
		else
			player.speed = -0xA0000
		end if

		CallFunction(PlayerObject_HandleRollAnimSpd)

		player.animation = ANI_WATERSLIDE

		CallFunction(PlayerObject_ResetOnFloor)

		if player.jumpPress == true
			CallFunction(PlayerObject_StartJump)
		end if

	end if

end function


function PlayerObject_HandleRunSpd2

	if player.speed > 0x10000

		player.timer = 0

		if player.speed < 0x5F5C2
			player.animation = ANI_WALKING
			CallFunction(PlayerObject_HandleWalkAnimSpd)
		else

			if player.speed > 0x9FFFF
				player.animation = ANI_PEELOUT
			else
				player.animation = ANI_RUNNING
			end if

			CallFunction(PlayerObject_HandleRunAnimSpd)

		end if

	end if

end function


event ObjectMain
	if stage.debugMode == true
		CallFunction(PlayerObject_ProcessPlayer)
		CheckEqual(options.attractMode, 0)
		temp0 = checkResult
		CheckEqual(inputPress.buttonB, 1)
		temp0 &= checkResult
		if temp0 == true
			player.type 			= TypeName[Debug Mode]
			player.yvel 			= 0
			player.state 			= PlayerObject_Blank
			player.frame 			= 0
			player.rotation 		= 0
			player.interaction 		= false
			player.drawOrder 		= 4
			player.priority 		= PRIORITY_ACTIVE
			player.invulnerable 	= 0
			player.visible 			= true
			player.abilityTimer 	= 0
			player.drownCounter 	= 0
			player.drownLevel 		= 0
			player.frame 			= debugMode.currentSelection
			screen.cameraEnabled 	= true
			screen.cameraStyle 		= 0
			player.hitboxLeft 		= HITBOX_AUTO
			player.hitboxRight 		= HITBOX_AUTO
			player.hitboxTop 		= HITBOX_AUTO
			player.hitboxBottom		= HITBOX_AUTO
			if stage.state == 3
				stage.state = STAGE_RUNNING
			end if
			if player[1].type == TypeName[Player 2 Object]
				player[1].priority = PRIORITY_ACTIVE
			end if
			if object[+playerCount].propertyValue == 3
				object[+playerCount].type = invincibilityType
				object[+playerCount].propertyValue = 0
			end if
		else
			if player.gravity == GRAVITY_GROUND
				player.doubleJumpState = 0
			end if
			CallFunction(player.state)
			ProcessAnimation()
			if player.entityPos == screen.cameraTarget
				if player.animation == ANI_JUMPING
					screen.adjustCameraY = player.jumpOffset
				else
					if screen.adjustCameraY == player.jumpOffset
						screen.adjustCameraY = 0
						player.iypos += player.jumpOffset
					end if
				end if
			end if

			if player.collisionEnabled == false
				temp0 = player.prevGravity
				player.prevGravity = player.gravity
				ProcessObjectMovement()
				player.prevGravity ^= 1
				CheckEqual(player.gravity, GRAVITY_GROUND)
				player.prevGravity |= checkResult
				player.prevGravity ^= 1
				if temp0 == true
					if player.prevGravity == 0
						player.enemyChainCounter = 0
						achieveRingCount = 0
						if player.animation == ANI_JUMPING
							if player.down == false
								if player.state != PlayerObject_BubbleBounce
									if player.state != PlayerObject_HandleRoll
										if player.state != PlayerObject_Unknown2 // "PlayerObject_HandleRollDecel"
											player.animation = ANI_WALKING
											if player.entityPos == screen.cameraTarget
												screen.adjustCameraY = 0
											end if
											player.iypos += player.jumpOffset
										end if
									end if
								end if
							end if
						end if
					end if
				end if
			else
				player.collisionEnabled = false
			end if
		end if
	else
		CallFunction(PlayerObject_ProcessPlayer)
		if player.gravity == GRAVITY_GROUND
			player.doubleJumpState = 0
		end if
		CallFunction(player.state)
		ProcessAnimation()
		if player.entityPos == screen.cameraTarget
			if player.animation == ANI_JUMPING
				screen.adjustCameraY = player.jumpOffset
			else
				if screen.adjustCameraY == player.jumpOffset
					screen.adjustCameraY = 0
					player.iypos += player.jumpOffset
				end if
			end if
		end if
		if player.collisionEnabled == false
			temp0 = player.prevGravity
			player.prevGravity = player.gravity
			ProcessObjectMovement()
			player.prevGravity ^= 1
			CheckEqual(player.gravity, GRAVITY_GROUND)
			player.prevGravity |= checkResult
			player.prevGravity ^= 1
			if temp0 == true
				if player.prevGravity == 0
					player.enemyChainCounter = 0
					achieveRingCount = 0
					if player.animation == ANI_JUMPING
						if player.down == false
							if player.state != PlayerObject_BubbleBounce
								if player.state != PlayerObject_HandleRoll
									if player.state != PlayerObject_Unknown2 // "PlayerObject_HandleRollDecel"
										player.animation = ANI_WALKING
										if player.entityPos == screen.cameraTarget
											screen.adjustCameraY = 0
										end if
										player.iypos += player.jumpOffset
									end if
								end if
							end if
						end if
					end if
				end if
			end if
		else
			player.collisionEnabled = false
		end if
	end if

	CallFunction(PlayerObject_ProcessSuperForms)
end event


event ObjectDraw

	if player.animation != player.prevAnimation
		player.prevAnimation = player.animation
		player.frame = 0
		player.animationTimer = 0
		player.animationSpeed = 0
	end if

	DrawObjectAnimation()

end event


event ObjectStartup

	playerCount = 0

	if options.attractMode == false

		if options.stageSelectFlag == false

			ReadSaveRAM()

			// Read from the "spindash" option and set things accordingly
			if saveRAM[35] == true
				options.spindash = true
				options.speedCap = false
				options.airSpeedCap = false
				options.spikeBehavior = false
			else
				options.spindash = false
				options.speedCap = true
				options.airSpeedCap = true
				options.spikeBehavior = true
			end if

		end if

	else

		options.spindash = true
		options.speedCap = false
		options.airSpeedCap = false
		options.spikeBehavior = false

	end if

	foreach (TypeName[Player Object], arrayPos0, ALL_ENTITIES)

		screen.cameraEnabled = true
		screen.cameraStyle = 0
		screen.cameraTarget = 0
		currentPlayer = 0

		// Handle S&T
		if stage.playerListPos > PlayerName[KNUCKLES]		// Only true with S&T
			stage.playerListPos = PlayerName[SONIC] 		// Set P1 to Sonic
			stage.player2Enabled = true						// Set P2 to true, will always be Tails
		end if

		// Set object slot 0 to the player object
		ResetObjectEntity(0, TypeName[Player Object], 0, object[arrayPos0].xpos, object[arrayPos0].ypos)

		screen.cameraX = player[0].ixpos
		screen.cameraY = player[0].iypos

		player[0].groupID = GROUP_PLAYERS
		player[0].state = PlayerObject_HandleAir
		player[0].priority = PRIORITY_ACTIVE
		player[0].drawOrder = DRAWORDER_PLAYER
		player[0].sortedDrawOrder = 4

		player[0].rollingDecelerationP = 0x2000

		// If the spindash is enabled, allow the player to spindash
		if options.spindash == true
			player[0].spindashFunction = PlayerObject_StartSpindash
		else
			player[0].spindashFunction = PlayerObject_StartJump
		end if
		
		// Set player hitboxes
		player[0].hitboxLeft 	= HITBOX_AUTO
		player[0].hitboxRight 	= HITBOX_AUTO
		player[0].hitboxTop 	= HITBOX_AUTO
		player[0].hitboxBottom 	= HITBOX_AUTO

		// Reset things for Super Sonic
		PlayerObject_SuperState = 0
		PlayerObject_SuperRingDecFrames = 0
		PlayerObject_SuperCounter1 = 0
		PlayerObject_SuperCounter2 = 0

		// Player specific stuff
		switch stage.playerListPos
		case PlayerName[SONIC]
			// Load animations
			LoadAnimation("SuperSonic.ani")
			LoadAnimation("Sonic.ani")

			// Initialize fur colors
			CallFunction(PlayerObject_HandleSuperPalSonic)

			// Set character values
			
			player[0].character = PLAYER_SONIC_ALONE
			
			// Offset by 5 pixels when rolling
			player[0].jumpOffset = -5 
			
			// Sonic's ability is the shield moves and Insta-shield
			player[0].jumpAbility = PlayerObject_HandleShieldAbilities
			break

		case PlayerName[TAILS]
			// Instead of object 0 being a player object, make it the Tails object
			player[0].type = TypeName[Tails Object]
			player[0].character = PLAYER_TAILS_ALONE

			// Initialize fur colors
			CallFunction(PlayerObject_HandleSuperPalTails)

			// Load animations
			LoadAnimation("Tails.ani")

			// Set character-specific values
			player[0].jumpOffset = -1 // Tails is shorter, so change this to account for that
			player[0].jumpAbility = PlayerObject_StartTailsFlight // Tails's ability is flight
			stage.player2Enabled = false // Tails can't have Miles following him
			break

		case PlayerName[KNUCKLES]
			// Load animations
			LoadAnimation("Knuckles.ani")

			// Initialize fur colors
			CallFunction(PlayerObject_HandleSuperPalKnux)

			player[0].character = PLAYER_KNUX_ALONE

			// Set Knuckles-specific stuff
			player[0].jumpOffset = -5 // Offset by 5 pixels when rolling
			player[0].jumpAbility = PlayerObject_StartKnuxGlide // Knuckles's ability is gliding

			// Disable Knuckles's "Peelout" animation from showing up
			ANI_PEELOUT = ANI_RUNNING

			// Force 2P Tails to be off if not on no-save
			if options.saveSlot > 0
				stage.player2Enabled = false
			end if
			break

		end switch

		// Set player count
		if stage.player2Enabled == true
			playerCount = 2
		else
			playerCount = 1
		end if

		// Reset currentPlayer
		currentPlayer = 0

		// Init physics
		CallFunction(PlayerObject_SwapPhysics)

		ResetObjectEntity(arrayPos0, TypeName[Blank Object], 0, 0, 0)

	next

	// Reset goggleType
	// -> Gets set by the goggles themselves in LZ
	goggleType = TypeName[Blank Object]

end event

event RSDKDraw
	DrawSprite(0)
end event

event RSDKLoad
	LoadSpriteSheet("Players/Sonic1.gif")
	SpriteFrame(-16, -19, 28, 39, 1, 1)
end event
